22
22
from colorama import Fore , Style
23
23
24
24
25
+ # Parse validation mode (only for legacy problem format version)
26
+ def parse_legacy_validation (mode : str ) -> set [str ]:
27
+ if mode == "default" :
28
+ return {mode }
29
+ else :
30
+ ok = True
31
+ parsed = set ()
32
+ for part in mode .split ():
33
+ if part in ["custom" , "interactive" , "multi-pass" ] and part not in parsed :
34
+ parsed .add (part )
35
+ else :
36
+ ok = False
37
+ if "custom" not in parsed or not ok :
38
+ fatal (f"Unrecognised validation mode { mode } ." )
39
+ return parsed
40
+
41
+
25
42
class ProblemLimits :
26
43
def __init__ (
27
44
self ,
@@ -104,7 +121,12 @@ def __init__(
104
121
105
122
106
123
class ProblemSettings :
107
- def __init__ (self , yamldata : dict [str , Any ], legacy_time_limit : Optional [float ] = None ):
124
+ def __init__ (
125
+ self ,
126
+ yamldata : dict [str , Any ],
127
+ problem : "Problem" ,
128
+ legacy_time_limit : Optional [float ] = None ,
129
+ ):
108
130
assert isinstance (yamldata , dict )
109
131
110
132
if "name" in yamldata and isinstance (yamldata ["name" ], str ):
@@ -120,7 +142,28 @@ def __init__(self, yamldata: dict[str, Any], legacy_time_limit: Optional[float]
120
142
)
121
143
if not self .is_legacy () and self .problem_format_version != "2023-07-draft" :
122
144
fatal (f"problem_format_version { self .problem_format_version } not supported" )
123
- # TODO: also support 'type'. For now, we only support legacy 'validation'.
145
+
146
+ if self .is_legacy ():
147
+ mode = parse_legacy_validation (parse_setting (yamldata , "validation" , "default" ))
148
+ else :
149
+ if "validation" in yamldata :
150
+ warn (
151
+ "problem.yaml: 'validation' is removed in 2023-07-draft, please use 'type' instead"
152
+ )
153
+ mode = set (parse_setting (yamldata , "type" , "pass-fail" ).split (" " ))
154
+ self .interactive : bool = "interactive" in mode
155
+ self .multi_pass : bool = "multi-pass" in mode
156
+ self .custom_output : bool = (
157
+ self .interactive
158
+ or self .multi_pass
159
+ or (
160
+ "custom" in mode
161
+ if self .is_legacy ()
162
+ # TODO #424: output_validator should be singular, but DOMjudge does not support this yet, so this should be fixed during export.
163
+ else (problem .path / "output_validators" ).exists ()
164
+ )
165
+ )
166
+
124
167
self .name : dict [str , str ] = parse_setting (yamldata , "name" , {"en" : "" })
125
168
self .uuid : str = parse_setting (yamldata , "uuid" , "" )
126
169
self .author : str = parse_setting (yamldata , "author" , "" )
@@ -129,7 +172,6 @@ def __init__(self, yamldata: dict[str, Any], legacy_time_limit: Optional[float]
129
172
self .license : str = parse_setting (yamldata , "license" , "unknown" )
130
173
self .rights_owner : str = parse_setting (yamldata , "rights_owner" , "" )
131
174
self .limits = ProblemLimits (parse_setting (yamldata , "limits" , {}), self , legacy_time_limit )
132
- self .validation : str = parse_setting (yamldata , "validation" , "default" )
133
175
self .validator_flags : list [str ] = parse_setting (yamldata , "validator_flags" , [])
134
176
self .keywords : str = parse_setting (yamldata , "keywords" , "" )
135
177
@@ -279,19 +321,20 @@ def _read_settings(self):
279
321
yaml_path .write_text (raw )
280
322
log ("Added new UUID to problem.yaml" )
281
323
282
- self .settings = ProblemSettings (yaml_data , self ._get_legacy_time_limit (yaml_data ))
283
- self .limits = self .settings .limits
324
+ self .settings = ProblemSettings (yaml_data , self , self ._get_legacy_time_limit (yaml_data ))
284
325
285
- mode = parse_validation (self .settings .validation )
286
- self .interactive = "interactive" in mode
287
- self .multipass = "multi-pass" in mode
326
+ # Aliasing fields makes life easier for us 😛
327
+ self .limits : ProblemLimits = self .settings .limits
328
+ self .interactive : bool = self .settings .interactive
329
+ self .multi_pass : bool = self .settings .multi_pass
330
+ self .custom_output : bool = self .settings .custom_output
288
331
289
332
# Handle dependencies...
290
333
has_validation_passes = self .limits .validation_passes is not None
291
- if self .multipass and not has_validation_passes :
334
+ if self .multi_pass and not has_validation_passes :
292
335
self .limits .validation_passes = 2
293
- if not self .multipass and has_validation_passes :
294
- warn ("limit: validation_passes is only used for multipass problems. SKIPPED." )
336
+ if not self .multi_pass and has_validation_passes :
337
+ warn ("limit: validation_passes is only used for multi_pass problems. SKIPPED." )
295
338
296
339
def _parse_testdata_yaml (p , path , bar ):
297
340
assert path .is_relative_to (p .path / "data" )
@@ -444,14 +487,14 @@ def testcases(
444
487
for f in in_paths :
445
488
t = testcase .Testcase (p , f , print_warn = True )
446
489
if (
447
- (p .interactive or p .multipass )
490
+ (p .interactive or p .multi_pass )
448
491
and mode == validate .Mode .INVALID
449
492
and t .root in ["invalid_answers" , "invalid_outputs" ]
450
493
):
451
494
msg = ""
452
495
if p .interactive :
453
496
msg += " interactive"
454
- if p .multipass :
497
+ if p .multi_pass :
455
498
msg += " multi-pass"
456
499
warn (f"Found file { f } for { mode } validation in{ msg } problem. Skipping." )
457
500
continue
@@ -504,7 +547,7 @@ def statement_samples(p) -> list[Path | tuple[Path, Path]]:
504
547
# Non-interactive and Non-multi-pass problems should not have .interaction files.
505
548
# On the other hand, interactive problems are allowed to have .{in,ans}.statement files,
506
549
# so that they can emulate a non-interactive problem with on-the-fly generated input.
507
- if not p .interactive and not p .multipass :
550
+ if not p .interactive and not p .multi_pass :
508
551
if len (interaction_paths ) != 0 :
509
552
warn (
510
553
f"Non-interactive/Non-multi-pass problem { p .name } should not have data/sample/*.interaction files."
@@ -672,13 +715,18 @@ def _validators(
672
715
return problem ._validators_cache [key ]
673
716
674
717
assert hasattr (cls , "source_dirs" )
718
+ # TODO #424: We should not support multiple output validators inside output_validator/.
675
719
paths = [p for source_dir in cls .source_dirs for p in glob (problem .path / source_dir , "*" )]
676
720
677
721
# Handle default output validation
678
- if cls == validate .OutputValidator and problem . settings . validation == "default" :
679
- if paths :
722
+ if cls == validate .OutputValidator :
723
+ if problem . settings . is_legacy () and not problem . custom_output and paths :
680
724
error ("Validation is default but custom output validator exists (ignoring it)" )
681
- paths = [config .TOOLS_ROOT / "support" / "default_output_validator.cpp" ]
725
+ paths = []
726
+ if not paths :
727
+ if problem .custom_output :
728
+ fatal ("Problem validation type requires output_validators/" )
729
+ paths = [config .TOOLS_ROOT / "support" / "default_output_validator.cpp" ]
682
730
683
731
# TODO: Instead of checking file contents, maybe specify this in generators.yaml?
684
732
def has_constraints_checking (f ):
@@ -906,12 +954,12 @@ def validate_data(problem, mode: validate.Mode, constraints: dict | bool | None
906
954
constraints = {}
907
955
assert constraints is None or isinstance (constraints , dict )
908
956
909
- if (problem .interactive or problem .multipass ) and mode == validate .Mode .ANSWER :
957
+ if (problem .interactive or problem .multi_pass ) and mode == validate .Mode .ANSWER :
910
958
if (problem .path / "answer_validators" ).exists ():
911
959
msg = ""
912
960
if problem .interactive :
913
961
msg += " interactive"
914
- if problem .multipass :
962
+ if problem .multi_pass :
915
963
msg += " multi-pass"
916
964
log (f"Not running answer_validators for{ msg } problems." )
917
965
return True
@@ -925,12 +973,12 @@ def validate_data(problem, mode: validate.Mode, constraints: dict | bool | None
925
973
testcases = problem .testcases (mode = mode )
926
974
case validate .Mode .ANSWER :
927
975
assert not problem .interactive
928
- assert not problem .multipass
976
+ assert not problem .multi_pass
929
977
problem .validators (validate .AnswerValidator , check_constraints = check_constraints )
930
978
testcases = problem .testcases (mode = mode )
931
979
case validate .Mode .INVALID :
932
980
problem .validators (validate .InputValidator )
933
- if not problem .interactive and not problem .multipass :
981
+ if not problem .interactive and not problem .multi_pass :
934
982
problem .validators (validate .AnswerValidator )
935
983
testcases = problem .testcases (mode = mode )
936
984
case _:
0 commit comments