cf-tool-cli 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
cf_tool/languages.py ADDED
@@ -0,0 +1,1195 @@
1
+ """Languages: how each one is built and run here, and what it is submitted as.
2
+
3
+ A language is named by its file extension (cpp, py, ...). cf-tool knows seven,
4
+ and config.toml can change any part of one, or add one:
5
+ - the compiler or interpreter: found on this machine, best first, unless set
6
+ - the build and run commands: made for it, unless set in full
7
+ - the Codeforces language: the setting, else the newest of its family
8
+ - the starter code for new problems: the setting, else a built-in one
9
+ C++ is compiled with the -std of the Codeforces language it is submitted as,
10
+ so code that compiles here compiles there too.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import json
16
+ import os
17
+ import re
18
+ import shutil
19
+ import subprocess
20
+ import sys
21
+ import time
22
+ from dataclasses import dataclass, field
23
+ from datetime import datetime
24
+ from pathlib import Path
25
+ from typing import TYPE_CHECKING, Callable, Mapping
26
+
27
+ from cf_tool import paths
28
+ from cf_tool.errors import CFError
29
+
30
+ if TYPE_CHECKING:
31
+ from cf_tool.settings import Settings
32
+
33
+
34
+ # ┌──────────────────────────────────────────────────────────┐
35
+ # │ Codeforces languages │
36
+ # └──────────────────────────────────────────────────────────┘
37
+
38
+ # NOTE: ids as Codeforces' submit form has them (checked against the cph
39
+ # \ extension's table, 2026-09); the live list replaces it once read
40
+ LANGS: dict[str, str] = {
41
+ "43": "GNU GCC C11 5.1.0",
42
+ "52": "Clang++17 Diagnostics",
43
+ "50": "GNU G++14 6.4.0",
44
+ "54": "GNU G++17 7.3.0",
45
+ "61": "GNU G++17 9.2.0 (64 bit, msys 2)",
46
+ "89": "GNU G++20 13.2 (64 bit, winlibs)",
47
+ "91": "GNU G++23 14.2 (64 bit, msys2)",
48
+ "59": "Microsoft Visual C++ 2017",
49
+ "65": "C# 8, .NET Core 3.1",
50
+ "79": "C# 10, .NET SDK 6.0",
51
+ "9": "C# Mono 6.8",
52
+ "28": "D DMD32 v2.105.0",
53
+ "32": "Go 1.22.2",
54
+ "12": "Haskell GHC 8.10.1",
55
+ "87": "Java 21 64bit",
56
+ "60": "Java 11.0.6",
57
+ "36": "Java 8 32bit",
58
+ "83": "Kotlin 1.7.20",
59
+ "88": "Kotlin 1.9.21",
60
+ "19": "OCaml 4.02.1",
61
+ "3": "Delphi 7",
62
+ "4": "Free Pascal 3.2.2",
63
+ "51": "PascalABC.NET 3.8.3",
64
+ "13": "Perl 5.20.1",
65
+ "6": "PHP 8.1.7",
66
+ "7": "Python 2.7.18",
67
+ "31": "Python 3.13.2",
68
+ "40": "PyPy 2.7.13 (7.3.0)",
69
+ "41": "PyPy 3.6.9 (7.3.0)",
70
+ "70": "PyPy 3.10 (7.3.15, 64bit)",
71
+ "67": "Ruby 3.2.2",
72
+ "75": "Rust 1.89.0 (2021)",
73
+ "98": "Rust 1.89.0 (2024)",
74
+ "20": "Scala 2.12.8",
75
+ "34": "JavaScript V8 4.8.0",
76
+ "55": "Node.js 15.8.0 (64bit)",
77
+ }
78
+
79
+ # Codeforces spells one language three ways: the submit menu ("GNU G++17
80
+ # 7.3.0"), the /my table and the API ("C++17 (GCC 7-32)"), and older names
81
+ # ("GNU C++17"). Rules are tried in order on the start of a name, so the more
82
+ # specific ones come first: Pascal before ".NET" C#, JavaScript before Java,
83
+ # C# and C++ before plain C.
84
+ _EXT_RULES: list[tuple[re.Pattern[str], str]] = [
85
+ (re.compile(p, re.IGNORECASE), ext)
86
+ for p, ext in [
87
+ (r"PascalABC|Delphi|FPC|Free Pascal", "pas"),
88
+ (r"JavaScript|Node\.js|V8", "js"),
89
+ (r"Java\b", "java"),
90
+ (r"Kotlin", "kt"),
91
+ (r"(Mono |MS )?C#", "cs"),
92
+ (r"F#", "fs"),
93
+ (r"(Microsoft )?Q#", "qs"),
94
+ (r".*(C\+\+|G\+\+)", "cpp"), # GNU G++17, C++20 (GCC 13-64), Clang++17, MS C++
95
+ (r"(GNU )?(GCC )?C\d*\b", "c"), # GNU C11, GNU GCC C11 5.1.0, C11 (GCC ...)
96
+ (r"Python|PyPy", "py"),
97
+ (r"Rust", "rs"),
98
+ (r"Go\b", "go"),
99
+ (r"Haskell", "hs"),
100
+ (r"OCaml", "ml"),
101
+ (r"Scala", "scala"),
102
+ (r"D\b", "d"),
103
+ (r"Perl", "pl"),
104
+ (r"PHP", "php"),
105
+ (r"Ruby", "rb"),
106
+ (r"(Active)?Tcl", "tcl"),
107
+ (r"Befunge", "bf"),
108
+ (r"Pike", "pike"),
109
+ (r"Io\b", "io"),
110
+ (r"Factor", "factor"),
111
+ (r"(Open)?Cobol", "cbl"),
112
+ (r"Ada\b", "adb"),
113
+ (r"FALSE\b", "f"),
114
+ (r"Secret", "secret_171"),
115
+ ]
116
+ ]
117
+
118
+ # family display names by file extension; unknown names are "Other" (txt)
119
+ FAMILY_NAMES = {
120
+ "cpp": "C++",
121
+ "c": "C",
122
+ "py": "Python",
123
+ "java": "Java",
124
+ "kt": "Kotlin",
125
+ "rs": "Rust",
126
+ "go": "Go",
127
+ "cs": "C#",
128
+ "fs": "F#",
129
+ "qs": "Q#",
130
+ "js": "JavaScript",
131
+ "pas": "Pascal",
132
+ "hs": "Haskell",
133
+ "ml": "OCaml",
134
+ "scala": "Scala",
135
+ "d": "D",
136
+ "pl": "Perl",
137
+ "php": "PHP",
138
+ "rb": "Ruby",
139
+ "tcl": "Tcl",
140
+ "bf": "Befunge",
141
+ "pike": "Pike",
142
+ "io": "Io",
143
+ "factor": "Factor",
144
+ "cbl": "COBOL",
145
+ "adb": "Ada",
146
+ "f": "FALSE",
147
+ "secret_171": "Secret",
148
+ }
149
+
150
+
151
+ def ext_of(lang_name: str) -> str:
152
+ """Source file extension for a Codeforces language name; "txt" when unknown."""
153
+ name = lang_name.strip()
154
+ for pattern, ext in _EXT_RULES:
155
+ if pattern.match(name):
156
+ return ext
157
+ return "txt"
158
+
159
+
160
+ def family_name(ext: str) -> str:
161
+ """Display name of the language family with this extension ("C++")."""
162
+ return FAMILY_NAMES.get(ext, "Other")
163
+
164
+
165
+ def newest_first_key(name: str) -> tuple:
166
+ """Sort key putting the highest version numbers in a name first."""
167
+ return tuple(-int(n) for n in re.findall(r"\d+", name)), name
168
+
169
+
170
+ def by_family(languages: Mapping[str, str]) -> dict[str, list[tuple[str, str]]]:
171
+ """(id, name) languages grouped by file extension, each group newest first."""
172
+ groups: dict[str, list[tuple[str, str]]] = {}
173
+ for lid, name in languages.items():
174
+ groups.setdefault(ext_of(name), []).append((lid, name))
175
+ return {
176
+ ext: sorted(group, key=lambda item: newest_first_key(item[1]))
177
+ for ext, group in groups.items()
178
+ }
179
+
180
+
181
+ def known_languages() -> dict[str, str]:
182
+ """Codeforces' languages, id -> name: the list last read from the submit
183
+ form, else the built-in table."""
184
+ try:
185
+ saved = json.loads((paths.STATE_DIR / "languages.json").read_text(encoding="utf-8"))
186
+ languages = saved["languages"]
187
+ if isinstance(languages, dict) and languages:
188
+ return {str(k): str(v) for k, v in languages.items()}
189
+ except (OSError, ValueError, KeyError, TypeError):
190
+ pass
191
+ return dict(LANGS)
192
+
193
+
194
+ def remember_languages(languages: Mapping[str, str]) -> None:
195
+ """Keep the submit form's list for later runs (setup without logging in)."""
196
+ if languages:
197
+ data = {"fetched": time.time(), "languages": dict(languages)}
198
+ paths.write_atomic(paths.STATE_DIR / "languages.json", json.dumps(data, indent=1) + "\n")
199
+
200
+
201
+ # ┌──────────────────────────────────────────────────────────┐
202
+ # │ Compilers and interpreters │
203
+ # └──────────────────────────────────────────────────────────┘
204
+
205
+
206
+ @dataclass(frozen=True)
207
+ class Tool:
208
+ """A compiler or interpreter found here."""
209
+
210
+ program: str # the command: "g++-14"
211
+ label: str # shown to the user: "GCC 14 (g++-14)"
212
+ banner: str = "" # the first line of its version text
213
+ kind: str = "" # "gcc", "clang", "apple clang", "python", "pypy", ...
214
+ version: tuple[int, ...] = () # (14, 2, 0); () when unknown
215
+
216
+
217
+ def _version_text(where: str, flag: str) -> str:
218
+ """First line of what `where flag` prints; "" when it won't run."""
219
+ if sys.platform == "darwin" and Path(where).parent == Path("/usr/bin"):
220
+ name = Path(where).name
221
+ # NOTE: Apple's placeholder commands open an installer dialog
222
+ # \ instead of running, unless their tools are installed
223
+ if name in _XCODE_STUBS and not _succeeds(["xcode-select", "-p"]):
224
+ return ""
225
+ if name in _JAVA_STUBS and not _succeeds(["/usr/libexec/java_home"]):
226
+ return ""
227
+ try:
228
+ done = subprocess.run([where, flag], capture_output=True, text=True, timeout=30)
229
+ except (OSError, subprocess.SubprocessError):
230
+ return ""
231
+ text = (done.stdout or done.stderr).strip() # NOTE: java and kotlinc print to stderr
232
+ return text.splitlines()[0] if done.returncode == 0 and text else ""
233
+
234
+
235
+ _XCODE_STUBS = {"cc", "c++", "gcc", "g++", "clang", "clang++", "python3"}
236
+ _JAVA_STUBS = {"java", "javac"}
237
+
238
+
239
+ def _succeeds(argv: list[str]) -> bool:
240
+ try:
241
+ return subprocess.run(argv, capture_output=True, timeout=10).returncode == 0
242
+ except (OSError, subprocess.SubprocessError):
243
+ return False
244
+
245
+
246
+ _banners: dict[str, str] | None = None # what tools said, kept in STATE_DIR/tools.json
247
+
248
+
249
+ def _tool_cache() -> dict[str, str]:
250
+ global _banners
251
+ if _banners is None:
252
+ try:
253
+ loaded = json.loads((paths.STATE_DIR / "tools.json").read_text(encoding="utf-8"))
254
+ _banners = loaded if isinstance(loaded, dict) else {}
255
+ except (OSError, ValueError):
256
+ _banners = {}
257
+ return _banners
258
+
259
+
260
+ def _remember(key: str, value: str) -> None:
261
+ cache = _tool_cache()
262
+ cache[key] = value
263
+ try:
264
+ paths.write_atomic(paths.STATE_DIR / "tools.json", json.dumps(cache, indent=1) + "\n")
265
+ except OSError:
266
+ pass
267
+
268
+
269
+ def _cached_version(program: str, flag: str) -> str:
270
+ """_version_text of the program on PATH, remembered across runs while the
271
+ program file stays the same (macOS's /usr/bin stand-ins are always asked)."""
272
+ where = shutil.which(program)
273
+ if where is None:
274
+ return ""
275
+ try:
276
+ stat = os.stat(where)
277
+ except OSError:
278
+ return ""
279
+ if sys.platform == "darwin" and Path(where).parent == Path("/usr/bin"):
280
+ return _version_text(where, flag) # NOTE: they forward to whichever tools are selected
281
+ key = f"{where}|{flag}|{stat.st_mtime_ns}|{stat.st_size}"
282
+ cache = _tool_cache()
283
+ if key in cache:
284
+ return cache[key]
285
+ banner = _version_text(where, flag)
286
+ if banner: # NOTE: a failure isn't remembered: the tool may be installed later
287
+ _remember(key, banner)
288
+ return banner
289
+
290
+
291
+ def _accepts_std(program: str, std: str) -> bool:
292
+ """Whether the C++ compiler program takes -std=std; asked once per
293
+ compiler version (by compiling nothing with it)."""
294
+ where = shutil.which(program)
295
+ banner = _cached_version(program, "--version") if where else ""
296
+ if not where or not banner:
297
+ return False
298
+ key = f"std|{banner}|{std}"
299
+ cache = _tool_cache()
300
+ if key in cache:
301
+ return cache[key] == "yes"
302
+ try:
303
+ done = subprocess.run(
304
+ [where, f"-std={std}", "-x", "c++", "-fsyntax-only", "-"],
305
+ stdin=subprocess.DEVNULL,
306
+ capture_output=True,
307
+ timeout=30,
308
+ )
309
+ except (OSError, subprocess.SubprocessError):
310
+ return False
311
+ _remember(key, "yes" if done.returncode == 0 else "no")
312
+ return done.returncode == 0
313
+
314
+
315
+ @dataclass
316
+ class Machine:
317
+ """What detection looks at: commands on PATH, their version text, the OS,
318
+ and which -std a C++ compiler takes (None: judged by its version)."""
319
+
320
+ which: Callable[[str], "str | None"] = shutil.which
321
+ version: Callable[[str, str], str] = _cached_version
322
+ platform: str = sys.platform
323
+ accepts: "Callable[[str, str], bool] | None" = _accepts_std
324
+
325
+
326
+ def _numbers(text: str) -> tuple[int, ...]:
327
+ """The first dotted version number in text: (14, 2, 0); () when none."""
328
+ match = re.search(r"\d+(?:\.\d+)+", text)
329
+ return tuple(int(n) for n in match.group().split(".")) if match else ()
330
+
331
+
332
+ def _gcc_family(machine: Machine, names: list[str]) -> list[Tool]:
333
+ """The GCC and clang compilers among names (C or C++):
334
+ - GCC before clang (Codeforces judges with GCC)
335
+ - one per compiler (macOS's g++ is clang++ again)"""
336
+ found: list[Tool] = []
337
+ seen: set[str] = set()
338
+ for program in names:
339
+ banner = machine.version(program, "--version") if machine.which(program) else ""
340
+ if not banner or banner in seen:
341
+ continue
342
+ seen.add(banner)
343
+ low = banner.lower()
344
+ kind = "apple clang" if "apple" in low else "clang" if "clang" in low else "gcc"
345
+ version = _numbers(banner)
346
+ title = {"apple clang": "Apple clang", "clang": "Clang", "gcc": "GCC"}[kind]
347
+ label = f"{title} {version[0] if version else '?'} ({program})"
348
+ found.append(Tool(program, label, banner, kind, version))
349
+ return sorted(found, key=lambda tool: tool.kind != "gcc")
350
+
351
+
352
+ def _detect_cpp(machine: Machine) -> list[Tool]:
353
+ names = [f"g++-{v}" for v in range(16, 10, -1)] + ["clang++", "g++"]
354
+ return _gcc_family(machine, names)
355
+
356
+
357
+ def _detect_c(machine: Machine) -> list[Tool]:
358
+ names = [f"gcc-{v}" for v in range(16, 10, -1)] + ["clang", "gcc"]
359
+ return _gcc_family(machine, names)
360
+
361
+
362
+ def _detect_python(machine: Machine) -> list[Tool]:
363
+ found: list[Tool] = []
364
+ seen: set[str] = set()
365
+ for program in ("python3", "python", "py", "pypy3"):
366
+ banner = machine.version(program, "--version") if machine.which(program) else ""
367
+ match = re.match(r"Python (3\.\d+)", banner)
368
+ if not match or banner in seen: # NOTE: python may be Python 2, or python3 again
369
+ continue
370
+ seen.add(banner)
371
+ kind = "pypy" if "pypy" in program else "python"
372
+ label = f"{'PyPy' if kind == 'pypy' else 'Python'} {match.group(1)} ({program})"
373
+ found.append(Tool(program, label, banner, kind, _numbers(banner)))
374
+ return found
375
+
376
+
377
+ def _detect_java(machine: Machine) -> list[Tool]:
378
+ banner = machine.version("java", "-version") if machine.which("java") else ""
379
+ match = re.search(r'version "(\d+)(?:\.(\d+))?', banner)
380
+ if not match:
381
+ return []
382
+ major = (
383
+ int(match.group(2) or 0) if match.group(1) == "1" else int(match.group(1))
384
+ ) # NOTE: "1.8.0" is Java 8
385
+ if major < 11:
386
+ return [] # NOTE: running a .java file directly, whatever its class, needs 11+
387
+ kind = "jdk" if machine.which("javac") else "jre"
388
+ return [Tool("java", f"Java {major} (java)", banner, kind, (major,))]
389
+
390
+
391
+ def _detect_kotlin(machine: Machine) -> list[Tool]:
392
+ if not (machine.which("kotlinc") and machine.which("java")):
393
+ return []
394
+ banner = machine.version("kotlinc", "-version")
395
+ match = re.search(r"kotlinc-jvm (\d+\.\d+)", banner)
396
+ if not match:
397
+ return []
398
+ return [
399
+ Tool("kotlinc", f"Kotlin {match.group(1)} (kotlinc)", banner, "kotlin", _numbers(banner))
400
+ ]
401
+
402
+
403
+ def _detect_rust(machine: Machine) -> list[Tool]:
404
+ banner = machine.version("rustc", "--version") if machine.which("rustc") else ""
405
+ match = re.search(r"rustc (\d+\.\d+)", banner)
406
+ if not match:
407
+ return []
408
+ return [Tool("rustc", f"Rust {match.group(1)} (rustc)", banner, "rust", _numbers(banner))]
409
+
410
+
411
+ def _detect_go(machine: Machine) -> list[Tool]:
412
+ banner = machine.version("go", "version") if machine.which("go") else ""
413
+ match = re.search(r"go(\d+\.\d+)", banner)
414
+ if not match:
415
+ return []
416
+ return [Tool("go", f"Go {match.group(1)} (go)", banner, "go", _numbers(match.group(1)))]
417
+
418
+
419
+ # ┌──────────────────────────────────────────────────────────┐
420
+ # │ Build and run commands │
421
+ # └──────────────────────────────────────────────────────────┘
422
+
423
+
424
+ @dataclass(frozen=True)
425
+ class Context:
426
+ """What a language's commands depend on besides the tool."""
427
+
428
+ platform: str
429
+ submit_name: str = "" # the Codeforces language it is submitted as ("" unknown)
430
+ flags: tuple[str, ...] = () # the user's extra flags
431
+ source: Path | None = None # the code file, when there is one (Java's class name)
432
+ accepts: "Callable[[str, str], bool] | None" = None # see Machine.accepts
433
+
434
+
435
+ # (build command, or None when nothing is built; run command). Words may hold
436
+ # the placeholders {src} {exe} {dir} {name} {class} {copy}, filled in by build.py.
437
+ Commands = tuple["list[str] | None", "list[str]"]
438
+
439
+
440
+ def _stack_flags(platform: str) -> list[str]:
441
+ """Linker flags giving a C/C++ program Codeforces' 256 MB stack (on Linux
442
+ a process limit does it instead, see process.py)."""
443
+ if platform == "darwin":
444
+ return ["-Wl,-stack_size,0x10000000"]
445
+ if platform == "win32":
446
+ return ["-Wl,--stack,268435456"]
447
+ return []
448
+
449
+
450
+ # each C++ standard's spellings for -std, the current one first
451
+ _STD_SPELLINGS = {
452
+ 26: ("c++26", "c++2c"),
453
+ 23: ("c++23", "c++2b"),
454
+ 20: ("c++20", "c++2a"),
455
+ 17: ("c++17", "c++1z"),
456
+ 14: ("c++14", "c++1y"),
457
+ 11: ("c++11", "c++0x"),
458
+ }
459
+ # the first major version of each compiler that knows each spelling
460
+ _FIRST_KNOWN = {
461
+ "gcc": {"c++23": 11, "c++2b": 11, "c++20": 10, "c++2a": 8, "c++17": 5, "c++1z": 5},
462
+ "clang": {"c++23": 17, "c++2b": 12, "c++20": 10, "c++2a": 5, "c++17": 5, "c++1z": 3},
463
+ "apple clang": {"c++23": 16, "c++2b": 13, "c++20": 12, "c++2a": 10, "c++17": 10, "c++1z": 6},
464
+ }
465
+
466
+
467
+ def cpp_standard(
468
+ submit_name: str,
469
+ tool: Tool | None = None,
470
+ accepts: Callable[[str, str], bool] | None = None,
471
+ ) -> str:
472
+ """The -std value for C++ submitted as submit_name ("GNU G++20 13.2" ->
473
+ c++20; unknown -> C++23): the newest spelling of that standard, or of an
474
+ older one, that tool takes. In order:
475
+ - asking the compiler (accepts), when it can be asked
476
+ - by the compiler's version, when it is known
477
+ - the standard's own name"""
478
+ match = re.search(r"\+\+\s*(\d{2})\b", submit_name)
479
+ year = int(match.group(1)) if match else 23
480
+ year = max((y for y in _STD_SPELLINGS if y <= year), default=11)
481
+ spellings = [
482
+ s for y in sorted(_STD_SPELLINGS, reverse=True) if y <= year for s in _STD_SPELLINGS[y]
483
+ ]
484
+ if tool is not None and accepts is not None:
485
+ for spelling in spellings:
486
+ if accepts(tool.program, spelling):
487
+ return spelling
488
+ first = _FIRST_KNOWN.get(tool.kind, {}) if tool is not None and tool.version else {}
489
+ if first:
490
+ major = tool.version[0] # type: ignore[union-attr]
491
+ for spelling in spellings:
492
+ if spelling in first and major >= first[spelling]:
493
+ return spelling
494
+ return spellings[0]
495
+
496
+
497
+ def _cpp_commands(tool: Tool, ctx: Context) -> Commands:
498
+ build = [
499
+ tool.program,
500
+ f"-std={cpp_standard(ctx.submit_name, tool, ctx.accepts)}",
501
+ "-O2",
502
+ "-Wall",
503
+ ]
504
+ build += _stack_flags(ctx.platform)
505
+ if "clang" in tool.kind:
506
+ build.append(f"-I{shim_folder()}") # NOTE: clang's own library has no bits/stdc++.h
507
+ build += [*ctx.flags, "{src}", "-o", "{exe}"]
508
+ return build, ["{exe}"]
509
+
510
+
511
+ def _c_commands(tool: Tool, ctx: Context) -> Commands:
512
+ match = re.search(r"\bC(\d{2})\b", ctx.submit_name)
513
+ build = [tool.program, f"-std=c{match.group(1) if match else '11'}", "-O2", "-Wall"]
514
+ build += _stack_flags(ctx.platform)
515
+ build += [*ctx.flags, "{src}", "-o", "{exe}", "-lm"]
516
+ return build, ["{exe}"]
517
+
518
+
519
+ def _python_commands(tool: Tool, ctx: Context) -> Commands:
520
+ return None, [tool.program, *ctx.flags, "{src}"]
521
+
522
+
523
+ _JAVA_CLASS_RE = re.compile(r"\bpublic\s+(?:final\s+|abstract\s+)*class\s+(\w+)")
524
+
525
+
526
+ def java_main_class(source: Path) -> str | None:
527
+ """The public class of a Java file; None when it has none (or is unreadable)."""
528
+ try:
529
+ match = _JAVA_CLASS_RE.search(source.read_text(encoding="utf-8", errors="replace"))
530
+ except OSError:
531
+ return None
532
+ return match.group(1) if match else None
533
+
534
+
535
+ def _java_commands(tool: Tool, ctx: Context) -> Commands:
536
+ """javac into the build folder, then java with the public class; without
537
+ javac, or no public class to name, java runs the file itself."""
538
+ main_class = java_main_class(ctx.source) if ctx.source is not None else None
539
+ if tool.kind != "jdk" or (ctx.source is not None and main_class is None):
540
+ return None, ["java", *ctx.flags, "{src}"]
541
+ # NOTE: javac wants a public class in a file of its name (a.java holds Main):
542
+ # \ it compiles a copy named for the class (see Setup.copy_as)
543
+ build = ["javac", "-encoding", "UTF-8", *ctx.flags, "-d", "{dir}", "{copy}"]
544
+ return build, ["java", "-cp", "{dir}", main_class or "{class}"]
545
+
546
+
547
+ def _kotlin_commands(tool: Tool, ctx: Context) -> Commands:
548
+ build = [tool.program, "{src}", "-include-runtime", *ctx.flags, "-d", "{dir}/{name}.jar"]
549
+ return build, ["java", "-jar", "{dir}/{name}.jar"]
550
+
551
+
552
+ def _rust_commands(tool: Tool, ctx: Context) -> Commands:
553
+ match = re.search(r"\((20\d\d)\)", ctx.submit_name)
554
+ edition = match.group(1) if match else "2021"
555
+ if edition == "2024" and tool.version and tool.version < (1, 85):
556
+ edition = "2021" # NOTE: rustc learned the 2024 edition in 1.85
557
+ build = [tool.program, "--edition", edition, "-O", *ctx.flags, "-o", "{exe}", "{src}"]
558
+ return build, ["{exe}"]
559
+
560
+
561
+ def _go_commands(tool: Tool, ctx: Context) -> Commands:
562
+ return [tool.program, "build", *ctx.flags, "-o", "{exe}", "{src}"], ["{exe}"]
563
+
564
+
565
+ # ┌──────────────────────────────────────────────────────────┐
566
+ # │ The languages │
567
+ # └──────────────────────────────────────────────────────────┘
568
+
569
+
570
+ @dataclass(frozen=True)
571
+ class Language:
572
+ """A language cf-tool knows: how to find its tools, how they build and
573
+ run a file, which Codeforces languages belong to it and which to prefer."""
574
+
575
+ key: str # "cpp": its file extension, and its table in config.toml
576
+ name: str # "C++"
577
+ extensions: tuple[str, ...] # ("cpp", "cc", "cxx")
578
+ detect: Callable[[Machine], list[Tool]]
579
+ commands: Callable[[Tool, Context], Commands]
580
+ starter: str # built-in starter code
581
+ prefer: tuple[str, ...] = () # patterns over Codeforces language names, best first
582
+ exclude: str = "" # a pattern of Codeforces languages never to pick
583
+ install: Mapping[str, str] = field(default_factory=dict) # platform -> how to get a tool
584
+ big_stack: bool = False # runs with Codeforces' stack (see process.py)
585
+
586
+ def install_hint(self, platform: str = sys.platform) -> str:
587
+ """How to install this language's tools on platform."""
588
+ return self.install.get(platform) or self.install["any"]
589
+
590
+
591
+ CPP_STARTER = """\
592
+ // {{name}}
593
+ // {{url}}
594
+ #include <bits/stdc++.h>
595
+ using namespace std;
596
+
597
+ int main() {
598
+ ios::sync_with_stdio(false);
599
+ cin.tie(nullptr);
600
+
601
+ return 0;
602
+ }
603
+ """
604
+
605
+ C_STARTER = """\
606
+ // {{name}}
607
+ // {{url}}
608
+ #include <stdio.h>
609
+
610
+ int main(void) {
611
+ return 0;
612
+ }
613
+ """
614
+
615
+ PYTHON_STARTER = """\
616
+ # {{name}}
617
+ # {{url}}
618
+ import sys
619
+
620
+ input = sys.stdin.readline
621
+
622
+
623
+ def main():
624
+ pass
625
+
626
+
627
+ main()
628
+ """
629
+
630
+ JAVA_STARTER = """\
631
+ // {{name}}
632
+ // {{url}}
633
+ import java.io.*;
634
+ import java.util.*;
635
+
636
+ public class Main {
637
+ public static void main(String[] args) throws IOException {
638
+ BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
639
+ PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out)));
640
+
641
+ out.flush();
642
+ }
643
+ }
644
+ """
645
+
646
+ KOTLIN_STARTER = """\
647
+ // {{name}}
648
+ // {{url}}
649
+ import java.io.*
650
+
651
+ fun main() {
652
+ val input = BufferedReader(InputStreamReader(System.`in`))
653
+ val out = PrintWriter(BufferedWriter(OutputStreamWriter(System.out)))
654
+
655
+ out.flush()
656
+ }
657
+ """
658
+
659
+ RUST_STARTER = """\
660
+ // {{name}}
661
+ // {{url}}
662
+ use std::io::{self, BufWriter, Read, Write};
663
+
664
+ fn main() {
665
+ let mut input = String::new();
666
+ io::stdin().read_to_string(&mut input).unwrap();
667
+ let mut tokens = input.split_ascii_whitespace();
668
+ let mut out = BufWriter::new(io::stdout().lock());
669
+
670
+ let _ = tokens.next();
671
+ out.flush().unwrap();
672
+ }
673
+ """
674
+
675
+ GO_STARTER = """\
676
+ // {{name}}
677
+ // {{url}}
678
+ package main
679
+
680
+ import (
681
+ \t"bufio"
682
+ \t"fmt"
683
+ \t"os"
684
+ )
685
+
686
+ func main() {
687
+ \tin := bufio.NewReader(os.Stdin)
688
+ \tout := bufio.NewWriter(os.Stdout)
689
+ \tdefer out.Flush()
690
+
691
+ \tvar n int
692
+ \tfmt.Fscan(in, &n)
693
+ }
694
+ """
695
+
696
+ LANGUAGES: dict[str, Language] = {
697
+ language.key: language
698
+ for language in (
699
+ Language(
700
+ "cpp",
701
+ "C++",
702
+ ("cpp", "cc", "cxx"),
703
+ _detect_cpp,
704
+ _cpp_commands,
705
+ CPP_STARTER,
706
+ prefer=(r"G\+\+\d+.*64", r"G\+\+\d+"),
707
+ exclude=r"Diagnostics|MS C\+\+|Microsoft|ZIP",
708
+ install={
709
+ "darwin": "brew install gcc",
710
+ "win32": "install MSYS2 (msys2.org) and its mingw-w64 g++",
711
+ "any": "install g++, e.g. sudo apt install g++",
712
+ },
713
+ big_stack=True,
714
+ ),
715
+ Language(
716
+ "c",
717
+ "C",
718
+ ("c",),
719
+ _detect_c,
720
+ _c_commands,
721
+ C_STARTER,
722
+ prefer=(r"C\d+",),
723
+ install={
724
+ "darwin": "brew install gcc",
725
+ "win32": "install MSYS2 (msys2.org) and its mingw-w64 gcc",
726
+ "any": "install gcc, e.g. sudo apt install gcc",
727
+ },
728
+ big_stack=True,
729
+ ),
730
+ Language(
731
+ "py",
732
+ "Python",
733
+ ("py",),
734
+ _detect_python,
735
+ _python_commands,
736
+ PYTHON_STARTER,
737
+ # NOTE: Codeforces itself recommends PyPy: the same code often runs many times faster
738
+ prefer=(r"PyPy 3.*64", r"PyPy 3", r"Python 3"),
739
+ exclude=r"^(Python|PyPy) 2",
740
+ install={"darwin": "brew install python", "any": "install Python 3 from python.org"},
741
+ ),
742
+ Language(
743
+ "java",
744
+ "Java",
745
+ ("java",),
746
+ _detect_java,
747
+ _java_commands,
748
+ JAVA_STARTER,
749
+ prefer=(r"Java \d+.*64", r"Java \d+"),
750
+ install={
751
+ "darwin": "brew install openjdk",
752
+ "any": "install a JDK 17 or newer, e.g. from adoptium.net",
753
+ },
754
+ ),
755
+ Language(
756
+ "kt",
757
+ "Kotlin",
758
+ ("kt",),
759
+ _detect_kotlin,
760
+ _kotlin_commands,
761
+ KOTLIN_STARTER,
762
+ prefer=(r"Kotlin",),
763
+ install={
764
+ "darwin": "brew install kotlin",
765
+ "any": "install kotlinc (kotlinlang.org) and a JDK",
766
+ },
767
+ ),
768
+ Language(
769
+ "rs",
770
+ "Rust",
771
+ ("rs",),
772
+ _detect_rust,
773
+ _rust_commands,
774
+ RUST_STARTER,
775
+ prefer=(r"Rust",),
776
+ install={"any": "install Rust from rustup.rs"},
777
+ ),
778
+ Language(
779
+ "go",
780
+ "Go",
781
+ ("go",),
782
+ _detect_go,
783
+ _go_commands,
784
+ GO_STARTER,
785
+ prefer=(r"Go \d",),
786
+ install={"darwin": "brew install go", "any": "install Go from go.dev/dl"},
787
+ ),
788
+ )
789
+ }
790
+
791
+
792
+ def language_key(ext_or_path: str | Path, settings: "Settings | None" = None) -> str | None:
793
+ """The language a file extension (or file) belongs to: a config.toml
794
+ table named for exactly that extension, else the built-in language that
795
+ has it; None when neither does."""
796
+ name = Path(str(ext_or_path)).name.lower()
797
+ if name.startswith(".") and name.count(".") == 1:
798
+ ext = name[1:] # NOTE: ".py", an extension on its own
799
+ else:
800
+ ext = Path(name).suffix[1:] if "." in name else name
801
+ if not ext:
802
+ return None
803
+ if settings is not None and ext in settings.languages:
804
+ return ext
805
+ return next((key for key, lang in LANGUAGES.items() if ext in lang.extensions), None)
806
+
807
+
808
+ def code_extensions(settings: "Settings | None" = None) -> set[str]:
809
+ """The file extensions of code cf-tool can build and run, with a dot."""
810
+ found = {f".{ext}" for lang in LANGUAGES.values() for ext in lang.extensions}
811
+ if settings is not None:
812
+ found |= {f".{key}" for key in settings.languages}
813
+ return found
814
+
815
+
816
+ def display_name(key: str) -> str:
817
+ """ "C++" for cpp; a language from config.toml goes by its family name."""
818
+ language = LANGUAGES.get(key)
819
+ if language is not None:
820
+ return language.name
821
+ name = family_name(key)
822
+ return name if name != "Other" else f".{key}"
823
+
824
+
825
+ # ┌──────────────────────────────────────────────────────────┐
826
+ # │ The language to submit │
827
+ # └──────────────────────────────────────────────────────────┘
828
+
829
+
830
+ def family(key: str, offered: Mapping[str, str]) -> list[tuple[str, str]]:
831
+ """The (id, name) Codeforces languages of a language's family, best first:
832
+ - by the first of its preference patterns a name matches
833
+ - then newest first, by the version numbers in the name"""
834
+ language = LANGUAGES.get(key)
835
+ prefer = language.prefer if language else ()
836
+ exclude = language.exclude if language else ""
837
+ members = [
838
+ (lid, name)
839
+ for lid, name in offered.items()
840
+ if ext_of(name) == key and not (exclude and re.search(exclude, name))
841
+ ]
842
+
843
+ def rank(item: tuple[str, str]):
844
+ name = item[1]
845
+ tier = next((i for i, p in enumerate(prefer) if re.search(p, name)), len(prefer))
846
+ return tier, *newest_first_key(name)
847
+
848
+ return sorted(members, key=rank)
849
+
850
+
851
+ def _normal(text: str) -> str:
852
+ """A language name for matching: no case, no spaces, C++ written G++."""
853
+ return re.sub(r"\s+", "", text.lower()).replace("c++", "g++")
854
+
855
+
856
+ def choose_submit(key: str, spec: str, offered: Mapping[str, str]) -> tuple[str, str] | None:
857
+ """The (id, name) to submit a language as, among offered:
858
+ - spec empty: the best of its family
859
+ - spec an id of its family (any id, for a language from config.toml)
860
+ - otherwise: the best language of the family whose name contains spec
861
+ ("G++20"; a number like 20 too), also among the ones never picked by
862
+ themselves (Clang++20 Diagnostics)
863
+ Never another family's language: None when nothing offered fits."""
864
+ options = family(key, offered)
865
+ if not spec:
866
+ return options[0] if options else None
867
+ custom = key not in LANGUAGES # NOTE: from config.toml: its family is unknown
868
+ if spec.isdigit() and spec in offered and (custom or ext_of(offered[spec]) == key):
869
+ return spec, offered[spec]
870
+ wanted = _normal(spec)
871
+ matches = [item for item in options if wanted in _normal(item[1])]
872
+ if not matches and not spec.isdigit():
873
+ others = [
874
+ item
875
+ for item in offered.items()
876
+ if (custom or ext_of(item[1]) == key) and wanted in _normal(item[1])
877
+ ]
878
+ matches = sorted(others, key=lambda item: newest_first_key(item[1]))
879
+ return matches[0] if matches else None
880
+
881
+
882
+ # ┌──────────────────────────────────────────────────────────┐
883
+ # │ Setting up │
884
+ # └──────────────────────────────────────────────────────────┘
885
+
886
+
887
+ @dataclass(frozen=True)
888
+ class Setup:
889
+ """How a language is built, run and submitted here."""
890
+
891
+ key: str # "cpp"
892
+ name: str # "C++"
893
+ tool: str # what builds or runs it: "GCC 14 (g++-14)", or the command from config.toml
894
+ build: tuple[str, ...] | None # None: nothing to build
895
+ run: tuple[str, ...]
896
+ fingerprint: str = "" # the tool's version text: part of the build cache key
897
+ submit: tuple[str, str] | None = None # the (id, name) it is submitted as, when known
898
+ big_stack: bool = False # run with Codeforces' stack
899
+ copy_as: str = "" # the name {copy} gives the code in the build folder ("": its own)
900
+
901
+
902
+ def setup(
903
+ key: str,
904
+ settings: "Settings",
905
+ *,
906
+ source: Path | None = None,
907
+ offered: Mapping[str, str] | None = None,
908
+ machine: Machine | None = None,
909
+ ) -> Setup:
910
+ """How to build and run a language here, and what to submit it as. Steps:
911
+ - the Codeforces language: the setting `submit`, else the family's best
912
+ - the tool: the setting `compiler`, else the best one found here
913
+ - the commands made for that tool and language, unless `build`/`run` are set
914
+ Raises CFError when no tool is found (with how to install one), or for an
915
+ unknown language without a `run` setting."""
916
+ machine = machine or Machine()
917
+ language = LANGUAGES.get(key)
918
+ conf = settings.language_settings(key)
919
+ offered = offered if offered is not None else known_languages()
920
+ submit = choose_submit(key, conf.submit, offered)
921
+ submit_name = submit[1] if submit else ""
922
+ build_words = list(conf.build) if conf.build is not None else None
923
+ run_words = list(conf.run) if conf.run is not None else None
924
+
925
+ if language is None:
926
+ if not run_words:
927
+ raise CFError(
928
+ f"cf-tool doesn't know .{key} files: give [{key}] a run command in "
929
+ f"{paths.config_file()}"
930
+ )
931
+ return Setup(
932
+ key,
933
+ display_name(key),
934
+ " ".join(run_words),
935
+ tuple(build_words) if build_words else None,
936
+ tuple(run_words),
937
+ fingerprint=" ".join(build_words or run_words),
938
+ submit=submit,
939
+ )
940
+
941
+ if conf.compiler:
942
+ tool = _configured_tool(language, conf.compiler, machine)
943
+ else:
944
+ tools = language.detect(machine)
945
+ if not tools:
946
+ raise CFError(
947
+ f"No {language.name} compiler or interpreter found. To get one: "
948
+ f"{language.install_hint(machine.platform)}"
949
+ )
950
+ tool = tools[0]
951
+ if key == "py" and "pypy" in submit_name.lower():
952
+ tool = next((t for t in tools if t.kind == "pypy"), tool) # NOTE: test as judged
953
+ ctx = Context(machine.platform, submit_name, conf.flags, source, machine.accepts)
954
+ build, run = language.commands(tool, ctx)
955
+ if "clang" in tool.kind and key == "cpp" and conf.build is None:
956
+ write_shim()
957
+ if conf.build is not None:
958
+ build = build_words or None
959
+ if run_words:
960
+ run = run_words
961
+ main_class = java_main_class(source) if key == "java" and source is not None else None
962
+ return Setup(
963
+ key,
964
+ language.name,
965
+ tool.label,
966
+ tuple(build) if build else None,
967
+ tuple(run),
968
+ fingerprint=tool.banner or tool.program,
969
+ submit=submit,
970
+ big_stack=language.big_stack,
971
+ copy_as=f"{main_class}.java" if main_class else "",
972
+ )
973
+
974
+
975
+ def _configured_tool(language: Language, program: str, machine: Machine) -> Tool:
976
+ """The tool named by `compiler` in config.toml, as detection would see it."""
977
+ if not machine.which(program):
978
+ raise CFError(
979
+ f"{program}, the compiler set for [{language.key}] in {paths.config_file()}, "
980
+ "is not installed"
981
+ )
982
+ flag = {"java": "-version", "kt": "-version", "go": "version"}.get(language.key, "--version")
983
+ banner = machine.version(program, flag)
984
+ low = banner.lower()
985
+ if language.key in ("cpp", "c"):
986
+ kind = "apple clang" if "apple" in low else "clang" if "clang" in low else "gcc"
987
+ elif language.key == "py":
988
+ kind = "pypy" if "pypy" in low or "pypy" in program else "python"
989
+ elif language.key == "java":
990
+ kind = "jdk" if machine.which("javac") else "jre"
991
+ else:
992
+ kind = language.key
993
+ return Tool(program, program, banner, kind, _numbers(banner))
994
+
995
+
996
+ # ┌──────────────────────────────────────────────────────────┐
997
+ # │ Starter code │
998
+ # └──────────────────────────────────────────────────────────┘
999
+
1000
+
1001
+ def starter(key: str, settings: "Settings", name: str = "template") -> tuple[str, str]:
1002
+ """The starter code for a new file of a language, and where it came from
1003
+ (a path, or "built-in"). In order:
1004
+ - templates/<name>.<ext> in the config folder, for a name other than
1005
+ "template" (cf new brute)
1006
+ - the setting `template`
1007
+ - templates/template.<ext>
1008
+ - the built-in starter (none for a language from config.toml: "")"""
1009
+ from cf_tool.workspace import read_source
1010
+
1011
+ folder = paths.templates_dir()
1012
+ if name != "template":
1013
+ named = folder / f"{name}.{key}"
1014
+ if named.is_file():
1015
+ return read_source(named), str(named)
1016
+ conf = settings.language_settings(key)
1017
+ if conf.template:
1018
+ path = Path(conf.template).expanduser()
1019
+ if not path.is_file():
1020
+ raise CFError(
1021
+ f"The starter file for [{key}] in {paths.config_file()}, {path}, doesn't exist"
1022
+ )
1023
+ return read_source(path), str(path)
1024
+ default = folder / f"template.{key}"
1025
+ if default.is_file():
1026
+ return read_source(default), str(default)
1027
+ language = LANGUAGES.get(key)
1028
+ return (language.starter if language else ""), "built-in"
1029
+
1030
+
1031
+ _PLACEHOLDER_RE = re.compile(r"\{\{\s*(\w+)\s*\}\}")
1032
+ # cf-tool 0.3's placeholders, still filled in
1033
+ _OLD_PLACEHOLDERS = {
1034
+ "$%U%$": "handle",
1035
+ "$%Y%$": "year",
1036
+ "$%M%$": "month",
1037
+ "$%D%$": "day",
1038
+ "$%h%$": "hour",
1039
+ "$%m%$": "minute",
1040
+ "$%s%$": "second",
1041
+ }
1042
+ _EMPTY_COMMENT_RE = re.compile(r"\s*(//|#|--|;)?\s*")
1043
+
1044
+
1045
+ def render(text: str, values: Mapping[str, str], now: datetime | None = None) -> str:
1046
+ """Starter code with its placeholders filled in: {{handle}} {{date}}
1047
+ {{time}} {{problem}} {{name}} {{url}}, and the old $%U%$ style. A comment
1048
+ line left empty (no problem to name, say) is dropped; an unknown
1049
+ placeholder stays as it is."""
1050
+ now = now or datetime.now()
1051
+ values = {
1052
+ "handle": "",
1053
+ "problem": "",
1054
+ "name": "",
1055
+ "url": "",
1056
+ "date": f"{now:%Y-%m-%d}",
1057
+ "time": f"{now:%H:%M}",
1058
+ "year": f"{now.year}",
1059
+ "month": f"{now.month:02d}",
1060
+ "day": f"{now.day:02d}",
1061
+ "hour": f"{now.hour:02d}",
1062
+ "minute": f"{now.minute:02d}",
1063
+ "second": f"{now.second:02d}",
1064
+ **values,
1065
+ }
1066
+ lines = []
1067
+ for line in text.splitlines(keepends=True):
1068
+ filled = _PLACEHOLDER_RE.sub(lambda m: values.get(m.group(1), m.group(0)), line)
1069
+ for placeholder, name in _OLD_PLACEHOLDERS.items():
1070
+ filled = filled.replace(placeholder, values.get(name, ""))
1071
+ if filled != line and _EMPTY_COMMENT_RE.fullmatch(filled.rstrip("\r\n")):
1072
+ continue # NOTE: a line of placeholders with nothing to fill in
1073
+ lines.append(filled)
1074
+ return "".join(lines)
1075
+
1076
+
1077
+ # ┌──────────────────────────────────────────────────────────┐
1078
+ # │ bits/stdc++.h for clang │
1079
+ # └──────────────────────────────────────────────────────────┘
1080
+
1081
+
1082
+ def shim_folder() -> Path:
1083
+ """The include folder holding cf-tool's bits/stdc++.h for clang."""
1084
+ return paths.CACHE_DIR / "include"
1085
+
1086
+
1087
+ def write_shim() -> Path:
1088
+ """Write (or refresh) cf-tool's bits/stdc++.h; returns the header."""
1089
+ header = shim_folder() / "bits" / "stdc++.h"
1090
+ try:
1091
+ current = header.read_text(encoding="utf-8")
1092
+ except OSError:
1093
+ current = ""
1094
+ if current != STDCXX_SHIM:
1095
+ paths.write_atomic(header, STDCXX_SHIM)
1096
+ return header
1097
+
1098
+
1099
+ STDCXX_SHIM = """\
1100
+ // bits/stdc++.h for compilers without one (clang, Apple clang): every
1101
+ // standard header, so `#include <bits/stdc++.h>` works as on Codeforces.
1102
+ // Written by cf-tool; changes here are overwritten.
1103
+ #ifndef CF_TOOL_BITS_STDCXX_H
1104
+ #define CF_TOOL_BITS_STDCXX_H
1105
+
1106
+ #include <cassert>
1107
+ #include <cctype>
1108
+ #include <cerrno>
1109
+ #include <cfloat>
1110
+ #include <cinttypes>
1111
+ #include <climits>
1112
+ #include <clocale>
1113
+ #include <cmath>
1114
+ #include <csetjmp>
1115
+ #include <csignal>
1116
+ #include <cstdarg>
1117
+ #include <cstddef>
1118
+ #include <cstdint>
1119
+ #include <cstdio>
1120
+ #include <cstdlib>
1121
+ #include <cstring>
1122
+ #include <ctime>
1123
+ #include <cwchar>
1124
+ #include <cwctype>
1125
+
1126
+ #include <algorithm>
1127
+ #include <array>
1128
+ #include <atomic>
1129
+ #include <bitset>
1130
+ #include <chrono>
1131
+ #include <complex>
1132
+ #include <condition_variable>
1133
+ #include <deque>
1134
+ #include <exception>
1135
+ #include <fstream>
1136
+ #include <functional>
1137
+ #include <initializer_list>
1138
+ #include <iomanip>
1139
+ #include <ios>
1140
+ #include <iosfwd>
1141
+ #include <iostream>
1142
+ #include <istream>
1143
+ #include <iterator>
1144
+ #include <limits>
1145
+ #include <list>
1146
+ #include <locale>
1147
+ #include <map>
1148
+ #include <memory>
1149
+ #include <mutex>
1150
+ #include <new>
1151
+ #include <numeric>
1152
+ #include <ostream>
1153
+ #include <queue>
1154
+ #include <random>
1155
+ #include <ratio>
1156
+ #include <regex>
1157
+ #include <set>
1158
+ #include <sstream>
1159
+ #include <stack>
1160
+ #include <stdexcept>
1161
+ #include <streambuf>
1162
+ #include <string>
1163
+ #include <system_error>
1164
+ #include <thread>
1165
+ #include <tuple>
1166
+ #include <type_traits>
1167
+ #include <typeindex>
1168
+ #include <typeinfo>
1169
+ #include <unordered_map>
1170
+ #include <unordered_set>
1171
+ #include <utility>
1172
+ #include <valarray>
1173
+ #include <vector>
1174
+
1175
+ #if __cplusplus >= 201703L
1176
+ #include <any>
1177
+ #include <charconv>
1178
+ #include <optional>
1179
+ #include <string_view>
1180
+ #include <variant>
1181
+ #endif
1182
+
1183
+ #if __cplusplus >= 202002L
1184
+ #include <bit>
1185
+ #include <compare>
1186
+ #include <concepts>
1187
+ #include <numbers>
1188
+ #include <span>
1189
+ #if __has_include(<ranges>)
1190
+ #include <ranges>
1191
+ #endif
1192
+ #endif
1193
+
1194
+ #endif
1195
+ """