rbx.cp 0.5.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.
Files changed (164) hide show
  1. rbx/__init__.py +0 -0
  2. rbx/annotations.py +127 -0
  3. rbx/autoenum.py +333 -0
  4. rbx/box/__init__.py +0 -0
  5. rbx/box/builder.py +77 -0
  6. rbx/box/cd.py +37 -0
  7. rbx/box/checkers.py +134 -0
  8. rbx/box/code.py +185 -0
  9. rbx/box/compile.py +56 -0
  10. rbx/box/conftest.py +42 -0
  11. rbx/box/contest/__init__.py +0 -0
  12. rbx/box/contest/build_contest_statements.py +347 -0
  13. rbx/box/contest/contest_package.py +76 -0
  14. rbx/box/contest/contest_utils.py +20 -0
  15. rbx/box/contest/main.py +179 -0
  16. rbx/box/contest/schema.py +155 -0
  17. rbx/box/contest/statements.py +82 -0
  18. rbx/box/creation.py +72 -0
  19. rbx/box/download.py +64 -0
  20. rbx/box/environment.py +345 -0
  21. rbx/box/extensions.py +26 -0
  22. rbx/box/generators.py +478 -0
  23. rbx/box/generators_test.py +63 -0
  24. rbx/box/main.py +449 -0
  25. rbx/box/package.py +316 -0
  26. rbx/box/packaging/boca/extension.py +27 -0
  27. rbx/box/packaging/boca/packager.py +245 -0
  28. rbx/box/packaging/contest_main.py +82 -0
  29. rbx/box/packaging/main.py +68 -0
  30. rbx/box/packaging/packager.py +117 -0
  31. rbx/box/packaging/polygon/packager.py +320 -0
  32. rbx/box/packaging/polygon/test.py +81 -0
  33. rbx/box/packaging/polygon/xml_schema.py +106 -0
  34. rbx/box/presets/__init__.py +503 -0
  35. rbx/box/presets/fetch.py +70 -0
  36. rbx/box/presets/lock_schema.py +20 -0
  37. rbx/box/presets/schema.py +59 -0
  38. rbx/box/schema.py +394 -0
  39. rbx/box/solutions.py +792 -0
  40. rbx/box/solutions_test.py +41 -0
  41. rbx/box/statements/__init__.py +0 -0
  42. rbx/box/statements/build_statements.py +359 -0
  43. rbx/box/statements/builders.py +375 -0
  44. rbx/box/statements/joiners.py +113 -0
  45. rbx/box/statements/latex.py +47 -0
  46. rbx/box/statements/latex_jinja.py +214 -0
  47. rbx/box/statements/schema.py +138 -0
  48. rbx/box/stresses.py +292 -0
  49. rbx/box/stressing/__init__.py +0 -0
  50. rbx/box/stressing/finder_parser.py +359 -0
  51. rbx/box/stressing/generator_parser.py +258 -0
  52. rbx/box/testcases.py +54 -0
  53. rbx/box/ui/__init__.py +0 -0
  54. rbx/box/ui/captured_log.py +372 -0
  55. rbx/box/ui/css/app.tcss +48 -0
  56. rbx/box/ui/main.py +38 -0
  57. rbx/box/ui/run.py +209 -0
  58. rbx/box/validators.py +245 -0
  59. rbx/box/validators_test.py +15 -0
  60. rbx/checker.py +128 -0
  61. rbx/clone.py +197 -0
  62. rbx/config.py +271 -0
  63. rbx/conftest.py +38 -0
  64. rbx/console.py +27 -0
  65. rbx/create.py +37 -0
  66. rbx/edit.py +24 -0
  67. rbx/grading/__init__.py +0 -0
  68. rbx/grading/caching.py +356 -0
  69. rbx/grading/conftest.py +33 -0
  70. rbx/grading/judge/__init__.py +0 -0
  71. rbx/grading/judge/cacher.py +503 -0
  72. rbx/grading/judge/digester.py +35 -0
  73. rbx/grading/judge/sandbox.py +748 -0
  74. rbx/grading/judge/sandboxes/__init__.py +0 -0
  75. rbx/grading/judge/sandboxes/isolate.py +683 -0
  76. rbx/grading/judge/sandboxes/stupid_sandbox.py +310 -0
  77. rbx/grading/judge/sandboxes/timeit.py +217 -0
  78. rbx/grading/judge/storage.py +284 -0
  79. rbx/grading/judge/test.py +38 -0
  80. rbx/grading/judge/testiso.py +54 -0
  81. rbx/grading/steps.py +522 -0
  82. rbx/grading/steps_with_caching.py +59 -0
  83. rbx/grading/steps_with_caching_run_test.py +429 -0
  84. rbx/grading_utils.py +148 -0
  85. rbx/hydration.py +101 -0
  86. rbx/main.py +122 -0
  87. rbx/metadata.py +105 -0
  88. rbx/providers/__init__.py +43 -0
  89. rbx/providers/codeforces.py +73 -0
  90. rbx/providers/provider.py +26 -0
  91. rbx/resources/checkers/boilerplate.cpp +20 -0
  92. rbx/resources/default_config.json +48 -0
  93. rbx/resources/envs/default.rbx.yml +37 -0
  94. rbx/resources/envs/isolate.rbx.yml +37 -0
  95. rbx/resources/packagers/boca/checker.sh +43 -0
  96. rbx/resources/packagers/boca/compare +53 -0
  97. rbx/resources/packagers/boca/compile/c +172 -0
  98. rbx/resources/packagers/boca/compile/cc +173 -0
  99. rbx/resources/packagers/boca/compile/cpp +172 -0
  100. rbx/resources/packagers/boca/compile/java +194 -0
  101. rbx/resources/packagers/boca/compile/kt +155 -0
  102. rbx/resources/packagers/boca/compile/pas +172 -0
  103. rbx/resources/packagers/boca/compile/py2 +173 -0
  104. rbx/resources/packagers/boca/compile/py3 +173 -0
  105. rbx/resources/packagers/boca/run/c +128 -0
  106. rbx/resources/packagers/boca/run/cc +128 -0
  107. rbx/resources/packagers/boca/run/cpp +128 -0
  108. rbx/resources/packagers/boca/run/java +194 -0
  109. rbx/resources/packagers/boca/run/kt +159 -0
  110. rbx/resources/packagers/boca/run/py2 +166 -0
  111. rbx/resources/packagers/boca/run/py3 +166 -0
  112. rbx/resources/presets/default/contest/contest.rbx.yml +14 -0
  113. rbx/resources/presets/default/contest/statement/contest.rbx.tex +97 -0
  114. rbx/resources/presets/default/contest/statement/olymp.sty +250 -0
  115. rbx/resources/presets/default/contest/statement/template.rbx.tex +42 -0
  116. rbx/resources/presets/default/preset.rbx.yml +12 -0
  117. rbx/resources/presets/default/problem/.gitignore +6 -0
  118. rbx/resources/presets/default/problem/gen.cpp +9 -0
  119. rbx/resources/presets/default/problem/problem.rbx.yml +44 -0
  120. rbx/resources/presets/default/problem/random.py +3 -0
  121. rbx/resources/presets/default/problem/random.txt +2 -0
  122. rbx/resources/presets/default/problem/sols/main.cpp +9 -0
  123. rbx/resources/presets/default/problem/sols/slow.cpp +15 -0
  124. rbx/resources/presets/default/problem/sols/wa.cpp +9 -0
  125. rbx/resources/presets/default/problem/statement/olymp.sty +250 -0
  126. rbx/resources/presets/default/problem/statement/projecao.png +0 -0
  127. rbx/resources/presets/default/problem/statement/statement.rbx.tex +18 -0
  128. rbx/resources/presets/default/problem/statement/template.rbx.tex +89 -0
  129. rbx/resources/presets/default/problem/tests/samples/000.in +1 -0
  130. rbx/resources/presets/default/problem/tests/samples/001.in +1 -0
  131. rbx/resources/presets/default/problem/validator.cpp +16 -0
  132. rbx/resources/presets/default/problem/wcmp.cpp +34 -0
  133. rbx/resources/templates/template.cpp +19 -0
  134. rbx/run.py +45 -0
  135. rbx/schema.py +64 -0
  136. rbx/submit.py +61 -0
  137. rbx/submitors/__init__.py +18 -0
  138. rbx/submitors/codeforces.py +120 -0
  139. rbx/submitors/submitor.py +25 -0
  140. rbx/test.py +347 -0
  141. rbx/testcase.py +70 -0
  142. rbx/testcase_rendering.py +79 -0
  143. rbx/testdata/box1/gen1.cpp +7 -0
  144. rbx/testdata/box1/gen2.cpp +9 -0
  145. rbx/testdata/box1/genScript.py +2 -0
  146. rbx/testdata/box1/hard-tle.sol.cpp +26 -0
  147. rbx/testdata/box1/ole.cpp +17 -0
  148. rbx/testdata/box1/problem.rbx.yml +39 -0
  149. rbx/testdata/box1/re.sol.cpp +23 -0
  150. rbx/testdata/box1/sol.cpp +22 -0
  151. rbx/testdata/box1/tests/1.in +1 -0
  152. rbx/testdata/box1/tle-and-incorrect.sol.cpp +33 -0
  153. rbx/testdata/box1/tle.sol.cpp +35 -0
  154. rbx/testdata/box1/validator.cpp +11 -0
  155. rbx/testdata/box1/wa.sol.cpp +22 -0
  156. rbx/testdata/caching/executable.py +1 -0
  157. rbx/testdata/compatible +0 -0
  158. rbx/testing_utils.py +65 -0
  159. rbx/utils.py +162 -0
  160. rbx_cp-0.5.0.dist-info/LICENSE +201 -0
  161. rbx_cp-0.5.0.dist-info/METADATA +89 -0
  162. rbx_cp-0.5.0.dist-info/RECORD +164 -0
  163. rbx_cp-0.5.0.dist-info/WHEEL +4 -0
  164. rbx_cp-0.5.0.dist-info/entry_points.txt +4 -0
@@ -0,0 +1,359 @@
1
+ import dataclasses
2
+ import pathlib
3
+ import typing
4
+ from enum import Enum
5
+ from typing import Callable, List, Optional
6
+
7
+ import lark
8
+ import typer
9
+
10
+ from rbx import console
11
+ from rbx.box import package
12
+ from rbx.box.schema import CodeItem, ExpectedOutcome
13
+ from rbx.grading.steps import CheckerResult, Outcome, RunLog
14
+
15
+ LARK_GRAMMAR = r"""
16
+ // A bunch of words
17
+ start: disjunction
18
+
19
+ disjunction: conjunction | disjunction _OR conjunction
20
+
21
+ conjunction: _atom | conjunction _AND _atom
22
+
23
+ _atom: statement | "(" disjunction ")" | negation
24
+ negation: _NOT "(" disjunction ")"
25
+
26
+ statement: solution matcher outcome checking?
27
+
28
+ solution: _filename | WILDCARD
29
+ outcome: CNAME
30
+ checking: "ON"i (checking_mode? checker | ":nil")
31
+ checking_mode: MODE ":"
32
+ MODE: "2" | "3"
33
+ checker: _filename | WILDCARD
34
+
35
+ // Operators
36
+ matcher: MATCHES | NOT_MATCHES
37
+
38
+ MATCHES: "~"
39
+ NOT_MATCHES: "!~"
40
+ _OR: "||"
41
+ _AND: "&&"
42
+ _NOT: "!"
43
+ WILDCARD: "$"
44
+
45
+ // File name
46
+ _filename: FILENAME | "\"" FILENAME "\""
47
+ FILENAME: /[\/A-Za-z0-9\-_\.]/+
48
+
49
+ %import common.CNAME
50
+ %ignore " "
51
+ """
52
+
53
+ LARK_PARSER = lark.Lark(LARK_GRAMMAR)
54
+
55
+
56
+ class CheckingMode(Enum):
57
+ THREE_WAY = 0
58
+ TWO_WAY = 1
59
+
60
+
61
+ @dataclasses.dataclass(frozen=True)
62
+ class FinderChecker:
63
+ path: str
64
+ mode: CheckingMode
65
+
66
+
67
+ @dataclasses.dataclass(frozen=True)
68
+ class FinderCall:
69
+ solution: str
70
+ expected_outcome: ExpectedOutcome
71
+ checker: Optional[FinderChecker]
72
+
73
+
74
+ @dataclasses.dataclass(frozen=True)
75
+ class FinderSolutionResult:
76
+ output_path: pathlib.Path
77
+ stderr_path: Optional[pathlib.Path] = None
78
+ run_log: Optional[RunLog] = None
79
+
80
+
81
+ @dataclasses.dataclass(frozen=True)
82
+ class FinderResult:
83
+ solution: str
84
+ outcome: Outcome
85
+ checker: Optional[FinderChecker]
86
+ truth_value: bool
87
+
88
+ # Auxiliary information.
89
+ solution_result: Optional[FinderSolutionResult] = None
90
+ checker_result: Optional[CheckerResult] = None
91
+
92
+
93
+ @dataclasses.dataclass(frozen=True)
94
+ class FinderOutcome:
95
+ truth_value: bool
96
+ results: List[FinderResult]
97
+
98
+
99
+ def or_outcome(a: FinderOutcome, b: FinderOutcome) -> FinderOutcome:
100
+ return FinderOutcome(
101
+ truth_value=a.truth_value or b.truth_value, results=a.results + b.results
102
+ )
103
+
104
+
105
+ def and_outcome(a: FinderOutcome, b: FinderOutcome) -> FinderOutcome:
106
+ return FinderOutcome(
107
+ truth_value=a.truth_value and b.truth_value, results=a.results + b.results
108
+ )
109
+
110
+
111
+ def get_checking_mode_from_string(mode: Optional[str]) -> CheckingMode:
112
+ if not mode:
113
+ return CheckingMode.THREE_WAY
114
+ if mode == '2':
115
+ return CheckingMode.TWO_WAY
116
+ return CheckingMode.THREE_WAY
117
+
118
+
119
+ def _get_main_checker() -> Optional[str]:
120
+ pkg = package.find_problem_package_or_die()
121
+ if not pkg.checker:
122
+ return None
123
+ return str(pkg.checker.path)
124
+
125
+
126
+ def _get_main_solution() -> Optional[str]:
127
+ sol = package.get_main_solution()
128
+ if sol is None:
129
+ return None
130
+ return str(sol.path)
131
+
132
+
133
+ def _get_default_checker_for_finder() -> Optional[FinderChecker]:
134
+ main_checker = _get_main_checker()
135
+ if main_checker is None:
136
+ return None
137
+ return FinderChecker(path=main_checker, mode=CheckingMode.THREE_WAY)
138
+
139
+
140
+ def _get_solution_from_token(token: lark.Token) -> str:
141
+ path = str(token)
142
+ if path == '$':
143
+ main_path = _get_main_solution()
144
+ assert main_path is not None
145
+ return main_path
146
+ return path
147
+
148
+
149
+ def _get_checker_from_token(token: lark.Token) -> str:
150
+ path = str(token)
151
+ if path == '$':
152
+ main_path = _get_main_checker()
153
+ assert main_path is not None
154
+ return main_path
155
+ return path
156
+
157
+
158
+ def _get_statement_checker(statement: lark.ParseTree) -> Optional[FinderChecker]:
159
+ checking_nodes = list(statement.find_data('checking'))
160
+ if not checking_nodes:
161
+ return _get_default_checker_for_finder()
162
+ (checking,) = checking_nodes
163
+
164
+ if not checking.children:
165
+ # Checking is nil
166
+ return None
167
+
168
+ if len(checking.children) == 1:
169
+ checker = typing.cast(lark.ParseTree, checking.children[0])
170
+ return FinderChecker(
171
+ path=_get_checker_from_token(typing.cast(lark.Token, checker.children[0])),
172
+ mode=CheckingMode.THREE_WAY,
173
+ )
174
+
175
+ mode = typing.cast(lark.ParseTree, checking.children[0])
176
+ checker = typing.cast(lark.ParseTree, checking.children[1])
177
+
178
+ return FinderChecker(
179
+ path=_get_checker_from_token(typing.cast(lark.Token, checker.children[0])),
180
+ mode=get_checking_mode_from_string(
181
+ typing.cast(lark.Token, mode.children[0]).value
182
+ ),
183
+ )
184
+
185
+
186
+ def get_all_solutions(tree: lark.ParseTree) -> List[str]:
187
+ solution_nodes = tree.find_data('solution')
188
+ res = set(
189
+ [
190
+ _get_solution_from_token(typing.cast(lark.Token, node.children[0]))
191
+ for node in solution_nodes
192
+ ]
193
+ )
194
+
195
+ if needs_expected_output(tree):
196
+ main_solution = package.get_main_solution()
197
+ assert main_solution is not None
198
+ res.add(str(main_solution.path))
199
+ return list(res)
200
+
201
+
202
+ def get_all_solution_items(tree: lark.ParseTree) -> List[CodeItem]:
203
+ solution_names = get_all_solutions(tree)
204
+ res = []
205
+
206
+ for solution_name in solution_names:
207
+ found_solution = package.get_solution_or_nil(solution_name)
208
+ if found_solution is None:
209
+ res.append(
210
+ CodeItem(
211
+ path=pathlib.Path(solution_name),
212
+ language=None,
213
+ compilationFiles=None,
214
+ )
215
+ )
216
+ continue
217
+ res.append(found_solution)
218
+
219
+ main_solution = package.get_main_solution()
220
+ if main_solution is None:
221
+ return res
222
+
223
+ for i, sol in enumerate(res):
224
+ if main_solution.path == sol.path:
225
+ res[i], res[0] = res[0], res[i]
226
+ return res
227
+
228
+
229
+ def _get_all_finder_checkers(tree: lark.ParseTree) -> List[FinderChecker]:
230
+ statement_nodes = tree.find_data('statement')
231
+ res = []
232
+
233
+ for statement_node in statement_nodes:
234
+ finder_checker = _get_statement_checker(statement_node)
235
+ if finder_checker is not None:
236
+ res.append(finder_checker)
237
+
238
+ return res
239
+
240
+
241
+ def get_all_checkers(tree: lark.ParseTree) -> List[str]:
242
+ return [finder_checker.path for finder_checker in _get_all_finder_checkers(tree)]
243
+
244
+
245
+ def get_all_checker_items(tree: lark.ParseTree) -> List[CodeItem]:
246
+ checker_names = get_all_checkers(tree)
247
+ res = []
248
+
249
+ for checker_name in checker_names:
250
+ main_checker = package.get_checker()
251
+ if str(main_checker.path) == checker_name:
252
+ res.append(main_checker)
253
+ continue
254
+ res.append(
255
+ CodeItem(
256
+ path=pathlib.Path(checker_name),
257
+ language=None,
258
+ compilationFiles=None,
259
+ )
260
+ )
261
+ return res
262
+
263
+
264
+ def needs_expected_output(tree: lark.ParseTree) -> bool:
265
+ finder_checkers = _get_all_finder_checkers(tree)
266
+ for finder_checker in finder_checkers:
267
+ if finder_checker.mode == CheckingMode.THREE_WAY:
268
+ return True
269
+ return False
270
+
271
+
272
+ def validate(tree: lark.ParseTree):
273
+ if needs_expected_output(tree):
274
+ if package.get_main_solution() is None:
275
+ console.console.print(
276
+ '[error]Finder expression requires three-way checking, but problem has no main solution.[/error]'
277
+ )
278
+ console.console.print(
279
+ 'Either provide an ACCEPTED solution at your problem.rbx.yml, or use two-way checking in your finder expression by providing the `2:` parameter before the checker name.'
280
+ )
281
+ raise typer.Exit(1)
282
+
283
+ all_checkers = get_all_checkers(tree)
284
+ for checker in all_checkers:
285
+ if not pathlib.Path(checker).is_file():
286
+ console.console.print(
287
+ f'[error]Finder expression references non-existing checker [item]{checker}[/item].[/error]'
288
+ )
289
+ raise typer.Exit(1)
290
+
291
+ all_solutions = get_all_solutions(tree)
292
+ for solution in all_solutions:
293
+ if not pathlib.Path(solution).is_file():
294
+ console.console.print(
295
+ f'[error]Finder expression references non-existing solution [item]{solution}[/item].[/error]'
296
+ )
297
+ raise typer.Exit(1)
298
+
299
+
300
+ @lark.v_args(inline=True)
301
+ class FinderTreeRunner(lark.Transformer):
302
+ outcome = ExpectedOutcome
303
+
304
+ def __init__(
305
+ self,
306
+ runner: Callable[[FinderCall], FinderResult],
307
+ ):
308
+ self.run_fn = runner
309
+
310
+ def solution(self, token: lark.Token) -> str:
311
+ return _get_solution_from_token(token)
312
+
313
+ def matcher(self, op: lark.Token) -> bool:
314
+ return op.value == '~'
315
+
316
+ @lark.v_args(inline=False, tree=True)
317
+ def statement(
318
+ self,
319
+ tree: lark.ParseTree,
320
+ ) -> FinderOutcome:
321
+ solution = typing.cast(str, tree.children[0])
322
+ is_positive = typing.cast(bool, tree.children[1])
323
+ expected_outcome = typing.cast(ExpectedOutcome, tree.children[2])
324
+
325
+ checker: Optional[FinderChecker] = _get_statement_checker(tree)
326
+
327
+ call = FinderCall(solution, expected_outcome=expected_outcome, checker=checker)
328
+ result = self.run_fn(call)
329
+ truth_value = result.truth_value
330
+ if not is_positive:
331
+ truth_value = not truth_value
332
+
333
+ return FinderOutcome(truth_value=truth_value, results=[result])
334
+
335
+ def negation(self, value: FinderOutcome) -> FinderOutcome:
336
+ return dataclasses.replace(value, truth_value=not value.truth_value)
337
+
338
+ @lark.v_args(inline=False)
339
+ def conjunction(self, values: List[FinderOutcome]) -> FinderOutcome:
340
+ res = FinderOutcome(truth_value=True, results=[])
341
+ for value in values:
342
+ res = and_outcome(res, value)
343
+ return res
344
+
345
+ @lark.v_args(inline=False)
346
+ def disjunction(self, values: List[FinderOutcome]) -> FinderOutcome:
347
+ res = FinderOutcome(truth_value=False, results=[])
348
+ for value in values:
349
+ res = or_outcome(res, value)
350
+ return res
351
+
352
+ def start(self, value: FinderOutcome) -> FinderOutcome:
353
+ return value
354
+
355
+
356
+ def parse(expression: str) -> lark.ParseTree:
357
+ tree = LARK_PARSER.parse(expression)
358
+ validate(tree)
359
+ return tree
@@ -0,0 +1,258 @@
1
+ # flake8: noqa
2
+ from typing import Any, Optional, Union
3
+ import typing
4
+
5
+ import random
6
+ import lark
7
+
8
+ LARK_GRAMMAR = r"""
9
+ start: args
10
+
11
+ // A bunch of args
12
+ args: (arg (_WS arg)*)?
13
+
14
+ // Argument shlex
15
+ arg: _block | random_hex
16
+
17
+ // Blocks
18
+ _block: (TEXT | _ticked | _expr)+
19
+
20
+ // Expression
21
+ _expr: var | range | select
22
+
23
+ // Ticked
24
+ _ticked: "`" _expr "`"
25
+
26
+ // Variables
27
+ var: "<" CNAME ">"
28
+
29
+ // Select
30
+ select: "(" select_value ("|" select_value)* ")"
31
+ select_value: _block
32
+
33
+ // Ranges
34
+ range: "[" range_value ".." range_value "]"
35
+ range_value: range | var | int | float | char
36
+ int: SIGNED_INT
37
+ float: SIGNED_FLOAT
38
+ char: "'" /./ "'"
39
+
40
+ // Random hex
41
+ random_hex.1: "@"
42
+
43
+ // Rest, strings, etc
44
+ TEXT: (/[^ \t\f\r\n\[\]\(\)\<\>\|\`]/ | ESCAPED_STRING)+
45
+
46
+ // Whitespace
47
+ _WS: WS
48
+
49
+ %import common.WS
50
+ %import common.CNAME
51
+ %import common.SIGNED_INT
52
+ %import common.SIGNED_FLOAT
53
+ %import common.ESCAPED_STRING
54
+ """
55
+
56
+ LARK_PARSER = lark.Lark(LARK_GRAMMAR)
57
+
58
+ Primitive = Union[int, float, str]
59
+
60
+
61
+ class GeneratorParsingError(Exception):
62
+ pass
63
+
64
+
65
+ class RandomInt:
66
+ def __init__(self, min: int, max: int):
67
+ self.min = min
68
+ self.max = max
69
+
70
+ def get(self) -> int:
71
+ if self.max < self.min:
72
+ raise GeneratorParsingError(
73
+ f'Found int range with invalid bounds [{self.min}..{self.max}].'
74
+ )
75
+ return random.randint(self.min, self.max)
76
+
77
+
78
+ class RandomChar:
79
+ def __init__(self, min: str, max: str):
80
+ self.min = min
81
+ self.max = max
82
+
83
+ def get(self) -> str:
84
+ if len(self.min) != 1 or len(self.max) != 1:
85
+ raise GeneratorParsingError(
86
+ f"Found char range with invalid bounds ['{self.min}'..'{self.max}']"
87
+ )
88
+ mn = ord(self.min)
89
+ mx = ord(self.max)
90
+ if mx < mn:
91
+ raise GeneratorParsingError(
92
+ f"Found char range with invalid bounds ['{self.min}'..'{self.max}']"
93
+ )
94
+ return chr(random.randint(mn, mx))
95
+
96
+
97
+ class RandomHex:
98
+ len: int
99
+
100
+ def __init__(self, len: int = 8):
101
+ self.len = len
102
+
103
+ def get(self) -> str:
104
+ return ''.join(random.choice('0123456789abcdef') for _ in range(self.len))
105
+
106
+
107
+ def parse(args: str) -> lark.ParseTree:
108
+ tree = LARK_PARSER.parse(args)
109
+ (args_root,) = tree.find_data('args')
110
+ return args_root
111
+
112
+
113
+ def _var_as_str(var: Any) -> str:
114
+ if isinstance(var, float):
115
+ return f'{var:.6f}'
116
+ return str(var)
117
+
118
+
119
+ def _down(node: lark.ParseTree) -> Union[lark.Token, lark.ParseTree]:
120
+ return node.children[0]
121
+
122
+
123
+ def _down_tree(node: lark.ParseTree) -> lark.ParseTree:
124
+ downed = _down(node)
125
+ return typing.cast(lark.ParseTree, downed)
126
+
127
+
128
+ def _down_token(node: lark.ParseTree) -> lark.Token:
129
+ downed = _down(node)
130
+ return typing.cast(lark.Token, downed)
131
+
132
+
133
+ def _is_primitive(x: Primitive) -> bool:
134
+ return isinstance(x, (int, float, str))
135
+
136
+
137
+ def _get_casting_type(a: Primitive, b: Primitive) -> Optional[str]:
138
+ if isinstance(a, int) and isinstance(b, int):
139
+ return 'int'
140
+ if isinstance(a, float) or isinstance(b, float):
141
+ if isinstance(a, (int, float)) or isinstance(b, (int, float)):
142
+ return 'float'
143
+ if isinstance(a, str) and isinstance(b, str):
144
+ return 'char'
145
+ return None
146
+
147
+
148
+ class Generator:
149
+ def __init__(self, vars):
150
+ self.vars = vars
151
+
152
+ def handle_var(self, expr: lark.ParseTree) -> Any:
153
+ name = typing.cast(lark.Token, _down(expr))
154
+ if name.value not in self.vars:
155
+ raise GeneratorParsingError(
156
+ f'Error parsing generator expression: variable {name.value} is not defined'
157
+ )
158
+ value = self.vars[name.value]
159
+ if not _is_primitive(value):
160
+ raise GeneratorParsingError(
161
+ f'Variable {name.value} has type {type(value)}, which is not supported by the Generator expression parser.'
162
+ )
163
+ return value
164
+
165
+ def handle_range_value(self, range_value: lark.ParseTree) -> Primitive:
166
+ tp = _down_tree(range_value)
167
+ if tp.data == 'int':
168
+ return int(_down_token(tp).value)
169
+ if tp.data == 'float':
170
+ return float(_down_token(tp).value)
171
+ if tp.data == 'char':
172
+ return str(_down_token(tp).value)
173
+ if tp.data == 'var':
174
+ return self.handle_var(tp)
175
+ return self.handle_range(tp)
176
+
177
+ def handle_range(self, range: lark.ParseTree) -> Primitive:
178
+ value_a, value_b = range.children[:2]
179
+
180
+ item_a = self.handle_range_value(typing.cast(lark.ParseTree, value_a))
181
+ item_b = self.handle_range_value(typing.cast(lark.ParseTree, value_b))
182
+
183
+ casting_type = _get_casting_type(item_a, item_b)
184
+ if casting_type is None:
185
+ raise GeneratorParsingError(
186
+ f'Types in range are uncompatible: {type(item_a)} != {type(item_b)}'
187
+ )
188
+
189
+ if casting_type == 'int':
190
+ return RandomInt(int(item_a), int(item_b)).get()
191
+ if casting_type == 'float':
192
+ return random.uniform(float(item_a), float(item_b))
193
+ if casting_type == 'char':
194
+ return RandomChar(str(item_a), str(item_b)).get()
195
+
196
+ raise GeneratorParsingError(
197
+ f'Types in range are not supported: {type(item_a)}, {type(item_b)}'
198
+ )
199
+
200
+ def handle_select_value(self, select_value: lark.ParseTree) -> str:
201
+ items = []
202
+ for item in select_value.children:
203
+ items.append(self.handle_block(item))
204
+ return ''.join(items)
205
+
206
+ def handle_select(self, select: lark.ParseTree) -> str:
207
+ options = []
208
+ for select_value in select.children:
209
+ options.append(
210
+ self.handle_select_value(typing.cast(lark.ParseTree, select_value))
211
+ )
212
+ return options[RandomInt(0, len(options) - 1).get()]
213
+
214
+ def handle_expr(self, expr: lark.ParseTree) -> str:
215
+ if expr.data == 'var':
216
+ return _var_as_str(self.handle_var(expr))
217
+ if expr.data == 'range':
218
+ return _var_as_str(self.handle_range(expr))
219
+ if expr.data == 'select':
220
+ return self.handle_select(expr)
221
+ raise GeneratorParsingError(
222
+ f'Internal error: found invalid AST node in Generator parsing, {expr.data}'
223
+ )
224
+
225
+ def handle_block(self, block: Union[lark.ParseTree, lark.Token]) -> str:
226
+ if isinstance(block, lark.Token):
227
+ return block.value
228
+
229
+ return self.handle_expr(block)
230
+
231
+ def generate_arg(self, arg: lark.ParseTree) -> str:
232
+ items = []
233
+ for arg_item in arg.children:
234
+ if hasattr(arg_item, 'data'):
235
+ data_item = typing.cast(lark.ParseTree, arg_item)
236
+ if data_item.data == 'random_hex':
237
+ items.append(RandomHex().get())
238
+ continue
239
+ items.append(self.handle_block(arg_item))
240
+ return ''.join(items)
241
+
242
+ def generate(self, args: lark.ParseTree) -> str:
243
+ args_list = []
244
+
245
+ for arg in args.children:
246
+ args_list.append(self.generate_arg(typing.cast(lark.ParseTree, arg)))
247
+
248
+ return ' '.join(args_list)
249
+
250
+
251
+ if __name__ == '__main__':
252
+ tree = parse(
253
+ """--MAX_N="allow me" --int=[1..<MAX_N>] --float=[1.0..<MAX_N>] --char=['a'..'a'] --selection="(a|b|(c|<MAX_N>))" --select=("a"|"b"|"c") @ --r2=[1..[8..15]]"""
254
+ )
255
+ print(tree)
256
+
257
+ generator = Generator({'MAX_N': 10})
258
+ print(generator.generate(tree))
rbx/box/testcases.py ADDED
@@ -0,0 +1,54 @@
1
+ import pathlib
2
+ import shutil
3
+ from typing import List
4
+
5
+ import typer
6
+ from pydantic import BaseModel
7
+
8
+ from rbx import console
9
+ from rbx.box import package
10
+ from rbx.box.package import get_build_testgroup_path, get_build_tests_path
11
+ from rbx.box.schema import Testcase, TestcaseGroup
12
+
13
+
14
+ class TestcaseData(BaseModel):
15
+ input: str
16
+ output: str
17
+
18
+
19
+ def find_built_testcases(group: TestcaseGroup) -> List[Testcase]:
20
+ inputs = find_built_testcase_inputs(group)
21
+
22
+ testcases = []
23
+ for input in inputs:
24
+ output = input.with_suffix('.out')
25
+ testcases.append(Testcase(inputPath=input, outputPath=output))
26
+ return testcases
27
+
28
+
29
+ def find_built_testcase_inputs(group: TestcaseGroup) -> List[pathlib.Path]:
30
+ testgroup_path = get_build_testgroup_path(group.name)
31
+ if not testgroup_path.is_dir():
32
+ console.console.print(
33
+ f'Testgroup {group.name} is not generated in build folder'
34
+ )
35
+ raise typer.Exit(1)
36
+
37
+ return sorted(testgroup_path.glob('*.in'))
38
+
39
+
40
+ def clear_built_testcases():
41
+ shutil.rmtree(str(get_build_tests_path()), ignore_errors=True)
42
+
43
+
44
+ def get_samples() -> List[Testcase]:
45
+ tcs = find_built_testcases(package.get_testgroup('samples'))
46
+ return [
47
+ Testcase(
48
+ inputPath=tc.inputPath.resolve(),
49
+ outputPath=tc.outputPath.resolve()
50
+ if tc.outputPath is not None and tc.outputPath.is_file()
51
+ else None,
52
+ )
53
+ for tc in tcs
54
+ ]
rbx/box/ui/__init__.py ADDED
File without changes