checktestdata 2026.3.3__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.
- checktestdata/__init__.py +0 -0
- checktestdata/__main__.py +3 -0
- checktestdata/lib.py +471 -0
- checktestdata/parser.py +493 -0
- checktestdata/pyctd.py +93 -0
- checktestdata/tokenizer.py +188 -0
- checktestdata-2026.3.3.dist-info/METADATA +17 -0
- checktestdata-2026.3.3.dist-info/RECORD +11 -0
- checktestdata-2026.3.3.dist-info/WHEEL +4 -0
- checktestdata-2026.3.3.dist-info/entry_points.txt +2 -0
- checktestdata-2026.3.3.dist-info/licenses/LICENSE +674 -0
|
File without changes
|
checktestdata/lib.py
ADDED
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import re
|
|
3
|
+
import sys
|
|
4
|
+
from abc import ABC
|
|
5
|
+
from collections import Counter
|
|
6
|
+
from enum import Enum
|
|
7
|
+
from fractions import Fraction
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
if hasattr(sys, "set_int_max_str_digits"):
|
|
11
|
+
sys.set_int_max_str_digits(0)
|
|
12
|
+
|
|
13
|
+
class _ValueType(ABC):
|
|
14
|
+
__slots__ = ("value",)
|
|
15
|
+
|
|
16
|
+
def __init__(self, value):
|
|
17
|
+
self.value = value
|
|
18
|
+
|
|
19
|
+
def __repr__(self):
|
|
20
|
+
return f"{self.__class__.__name__}({repr(self.value)})"
|
|
21
|
+
|
|
22
|
+
def __str__(self):
|
|
23
|
+
return f"{self.__class__.__name__}({self.value})"
|
|
24
|
+
|
|
25
|
+
def __bool__(self):
|
|
26
|
+
raise TypeError(f"object of type '{self.__class__.__name__}' has no bool()")
|
|
27
|
+
|
|
28
|
+
def __invert__(self):
|
|
29
|
+
raise TypeError(f"bad operand type for unary !: '{self.__class__.__name__}'")
|
|
30
|
+
|
|
31
|
+
class Boolean(_ValueType):
|
|
32
|
+
__slots__ = ()
|
|
33
|
+
|
|
34
|
+
@staticmethod
|
|
35
|
+
def _check_combine_type(lhs, rhs):
|
|
36
|
+
if lhs.__class__ != rhs.__class__:
|
|
37
|
+
raise TypeError(f"cannot combine {lhs.__class__.__name__} and {rhs.__class__.__name__}")
|
|
38
|
+
|
|
39
|
+
def __init__(self, value):
|
|
40
|
+
assert isinstance(value, bool)
|
|
41
|
+
super().__init__(value)
|
|
42
|
+
|
|
43
|
+
def __bool__(self):
|
|
44
|
+
return self.value
|
|
45
|
+
|
|
46
|
+
def __invert__(self):
|
|
47
|
+
return Boolean(not self.value)
|
|
48
|
+
|
|
49
|
+
def __and__(self, other):
|
|
50
|
+
Boolean._check_combine_type(self, other)
|
|
51
|
+
return Boolean(self.value and other.value)
|
|
52
|
+
|
|
53
|
+
def __or__(self, other):
|
|
54
|
+
Boolean._check_combine_type(self, other)
|
|
55
|
+
return Boolean(self.value or other.value)
|
|
56
|
+
|
|
57
|
+
class _CompareableValue(_ValueType, ABC):
|
|
58
|
+
__slots__ = ()
|
|
59
|
+
|
|
60
|
+
@staticmethod
|
|
61
|
+
def _check_compare_type(lhs, rhs):
|
|
62
|
+
if lhs.__class__ != rhs.__class__:
|
|
63
|
+
raise TypeError(f"cannot compare {lhs.__class__.__name__} and {rhs.__class__.__name__}")
|
|
64
|
+
|
|
65
|
+
def __hash__(self):
|
|
66
|
+
return hash(self.value)
|
|
67
|
+
|
|
68
|
+
def __eq__(self, other):
|
|
69
|
+
_CompareableValue._check_compare_type(self, other)
|
|
70
|
+
return Boolean(self.value == other.value)
|
|
71
|
+
|
|
72
|
+
def __ne__(self, other):
|
|
73
|
+
_CompareableValue._check_compare_type(self, other)
|
|
74
|
+
return Boolean(self.value != other.value)
|
|
75
|
+
|
|
76
|
+
def __lt__(self, other):
|
|
77
|
+
_CompareableValue._check_compare_type(self, other)
|
|
78
|
+
return Boolean(self.value < other.value)
|
|
79
|
+
|
|
80
|
+
def __le__(self, other):
|
|
81
|
+
_CompareableValue._check_compare_type(self, other)
|
|
82
|
+
return Boolean(self.value <= other.value)
|
|
83
|
+
|
|
84
|
+
def __ge__(self, other):
|
|
85
|
+
_CompareableValue._check_compare_type(self, other)
|
|
86
|
+
return Boolean(self.value >= other.value)
|
|
87
|
+
|
|
88
|
+
def __gt__(self, other):
|
|
89
|
+
_CompareableValue._check_compare_type(self, other)
|
|
90
|
+
return Boolean(self.value > other.value)
|
|
91
|
+
|
|
92
|
+
class String(_CompareableValue):
|
|
93
|
+
__slots__ = ()
|
|
94
|
+
|
|
95
|
+
def __init__(self, value):
|
|
96
|
+
assert isinstance(value, str)
|
|
97
|
+
super().__init__(value)
|
|
98
|
+
|
|
99
|
+
class Number(_CompareableValue):
|
|
100
|
+
__slots__ = ()
|
|
101
|
+
|
|
102
|
+
@staticmethod
|
|
103
|
+
def _check_combine_type(lhs, rhs):
|
|
104
|
+
if lhs.__class__ != rhs.__class__:
|
|
105
|
+
raise TypeError(f"cannot combine {lhs.__class__.__name__} and {rhs.__class__.__name__}")
|
|
106
|
+
|
|
107
|
+
def __init__(self, value):
|
|
108
|
+
assert isinstance(value, (int, Fraction))
|
|
109
|
+
super().__init__(value)
|
|
110
|
+
|
|
111
|
+
def is_integer(self):
|
|
112
|
+
# we check the type, not the value!
|
|
113
|
+
return isinstance(self.value, int)
|
|
114
|
+
|
|
115
|
+
def __index__(self):
|
|
116
|
+
if not self.is_integer():
|
|
117
|
+
raise TypeError("expected integer but got float")
|
|
118
|
+
return self.value
|
|
119
|
+
|
|
120
|
+
def __int__(self):
|
|
121
|
+
if not self.is_integer():
|
|
122
|
+
raise TypeError("expected integer but got float")
|
|
123
|
+
return self.value
|
|
124
|
+
|
|
125
|
+
def __neg__(self):
|
|
126
|
+
return Number(-self.value)
|
|
127
|
+
|
|
128
|
+
def __add__(self, other):
|
|
129
|
+
Number._check_combine_type(self, other)
|
|
130
|
+
return Number(self.value + other.value)
|
|
131
|
+
|
|
132
|
+
def __sub__(self, other):
|
|
133
|
+
Number._check_combine_type(self, other)
|
|
134
|
+
return Number(self.value - other.value)
|
|
135
|
+
|
|
136
|
+
def __mul__(self, other):
|
|
137
|
+
Number._check_combine_type(self, other)
|
|
138
|
+
return Number(self.value * other.value)
|
|
139
|
+
|
|
140
|
+
def __mod__(self, other):
|
|
141
|
+
Number._check_combine_type(self, other)
|
|
142
|
+
if self.is_integer() and other.is_integer():
|
|
143
|
+
res = self.value % other.value
|
|
144
|
+
if res != 0 and (self.value < 0) != (other.value < 0):
|
|
145
|
+
res -= other.value
|
|
146
|
+
return Number(res)
|
|
147
|
+
else:
|
|
148
|
+
#seems to be an error in Checktestdata
|
|
149
|
+
raise TypeError(f"can only perform modulo on integers")
|
|
150
|
+
#return Number(self.value % other.value)
|
|
151
|
+
|
|
152
|
+
def __truediv__(self, other):
|
|
153
|
+
Number._check_combine_type(self, other)
|
|
154
|
+
if self.is_integer() and other.is_integer():
|
|
155
|
+
res = abs(self.value) // abs(other.value)
|
|
156
|
+
if (self.value < 0) != (other.value < 0):
|
|
157
|
+
res = -res
|
|
158
|
+
return Number(res)
|
|
159
|
+
else:
|
|
160
|
+
return Number(self.value / other.value)
|
|
161
|
+
|
|
162
|
+
def __pow__(self, other):
|
|
163
|
+
if not other.is_integer() or other.value < 0 or other.value.bit_length() > sys.maxsize.bit_length() + 1:
|
|
164
|
+
raise TypeError(f"exponent must be an unsigned long")
|
|
165
|
+
return Number(self.value ** other.value)
|
|
166
|
+
|
|
167
|
+
class VarType:
|
|
168
|
+
__slots__ = ("name", "data", "entries", "value_count")
|
|
169
|
+
|
|
170
|
+
def __init__(self, name):
|
|
171
|
+
self.name = name
|
|
172
|
+
self.data = None
|
|
173
|
+
self.entries = {}
|
|
174
|
+
self.value_count = Counter()
|
|
175
|
+
|
|
176
|
+
def reset(self):
|
|
177
|
+
self.data = None
|
|
178
|
+
self.entries = {}
|
|
179
|
+
self.value_count = Counter()
|
|
180
|
+
|
|
181
|
+
def __getitem__(self, key):
|
|
182
|
+
if key == None:
|
|
183
|
+
if self.entries:
|
|
184
|
+
raise RuntimeError(f"{self.name} is an array")
|
|
185
|
+
if self.data is None:
|
|
186
|
+
raise RuntimeError(f"{self.name} is not assigned")
|
|
187
|
+
return self.data
|
|
188
|
+
else:
|
|
189
|
+
if self.data is not None:
|
|
190
|
+
raise RuntimeError(f"{self.name} is not an array")
|
|
191
|
+
if key not in self.entries:
|
|
192
|
+
raise RuntimeError(f"missing key in {self.name}")
|
|
193
|
+
return self.entries[key]
|
|
194
|
+
|
|
195
|
+
def __setitem__(self, key, value):
|
|
196
|
+
# TODO: handle var_a[None] = var_b[None]?
|
|
197
|
+
assert isinstance(value, _ValueType), self.name
|
|
198
|
+
if key == None:
|
|
199
|
+
if self.entries:
|
|
200
|
+
raise RuntimeError(f"cannot replace array {self.name} with single value")
|
|
201
|
+
self.data = value
|
|
202
|
+
else:
|
|
203
|
+
if self.data is not None:
|
|
204
|
+
raise RuntimeError(f"{self.name} is not an array")
|
|
205
|
+
for key_part in key:
|
|
206
|
+
# Checktestdata seems to enforce integers here
|
|
207
|
+
if not isinstance(key_part, Number) or not key_part.is_integer():
|
|
208
|
+
raise TypeError(f"key for {self.name} must be integer(s)")
|
|
209
|
+
if key in self.entries:
|
|
210
|
+
self.value_count[self.entries[key]] -= 1
|
|
211
|
+
self.entries[key] = value
|
|
212
|
+
self.value_count[value] += 1
|
|
213
|
+
|
|
214
|
+
def assert_array(method, arg):
|
|
215
|
+
if not isinstance(arg, VarType):
|
|
216
|
+
raise TypeError(f"{method} cannot be invoked with {arg.__class__.__name__}")
|
|
217
|
+
if arg.data is not None:
|
|
218
|
+
raise TypeError(f"{method} must be invoked with an array, but {arg.name} is a value")
|
|
219
|
+
|
|
220
|
+
def assert_type(method, arg, t):
|
|
221
|
+
if not isinstance(arg, t):
|
|
222
|
+
raise TypeError(f"{method} cannot be invoked with {arg.__class__.__name__}")
|
|
223
|
+
|
|
224
|
+
def msg_text(text):
|
|
225
|
+
special = {
|
|
226
|
+
" ": "<SPACE>",
|
|
227
|
+
"\n": "<NEWLINE>",
|
|
228
|
+
"": "<EOF>",
|
|
229
|
+
}
|
|
230
|
+
return special.get(text, text)
|
|
231
|
+
|
|
232
|
+
INTEGER_REGEX = re.compile(r"0|-?[1-9][0-9]*")
|
|
233
|
+
FLOAT_PARTS = re.compile(r"-?([0-9]*)(?:\.([0-9]*))?(?:[eE](.*))?")
|
|
234
|
+
class FLOAT_REGEX(Enum):
|
|
235
|
+
ANY = re.compile(r"-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][+-]?(?:0|[1-9][0-9]*))?")
|
|
236
|
+
FIXED = re.compile(r"-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?")
|
|
237
|
+
SCIENTIFIC = re.compile(r"-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][+-]?(?:0|[1-9][0-9]*))")
|
|
238
|
+
|
|
239
|
+
def msg(self):
|
|
240
|
+
return "float" if self == FLOAT_REGEX.ANY else f"{self.name.lower()} float"
|
|
241
|
+
|
|
242
|
+
class _Reader:
|
|
243
|
+
def __init__(self, raw):
|
|
244
|
+
self.raw = raw
|
|
245
|
+
self.pos = 0
|
|
246
|
+
self.line = 1
|
|
247
|
+
self.column = 1
|
|
248
|
+
self.space_tokenizer = re.compile(r'[\s]|[^\s]*', re.DOTALL | re.MULTILINE)
|
|
249
|
+
|
|
250
|
+
def _advance(self, text):
|
|
251
|
+
self.pos += len(text)
|
|
252
|
+
newline = text.find("\n")
|
|
253
|
+
if newline >= 0:
|
|
254
|
+
self.line += text.count("\n")
|
|
255
|
+
self.column = len(text) - newline
|
|
256
|
+
else:
|
|
257
|
+
self.column += len(text)
|
|
258
|
+
|
|
259
|
+
def peek_char(self):
|
|
260
|
+
return self.raw[self.pos:self.pos+1]
|
|
261
|
+
|
|
262
|
+
def peek_until_space(self):
|
|
263
|
+
return self.space_tokenizer.match(self.raw, self.pos).group()
|
|
264
|
+
|
|
265
|
+
def pop_string(self, expected):
|
|
266
|
+
if not self.raw.startswith(expected, self.pos):
|
|
267
|
+
got = self.raw[self.pos:self.pos+len(expected)]
|
|
268
|
+
msg = f"{self.line}:{self.column} got: {msg_text(got)}, but expected {msg_text(expected)}"
|
|
269
|
+
raise RuntimeError(msg)
|
|
270
|
+
self._advance(expected)
|
|
271
|
+
|
|
272
|
+
def pop_regex(self, regex):
|
|
273
|
+
match = regex.match(self.raw, self.pos)
|
|
274
|
+
if not match:
|
|
275
|
+
got = self.peek_until_space()
|
|
276
|
+
msg = f"{self.line}:{self.column} got: {msg_text(got)}, but expected '{regex.pattern}'"
|
|
277
|
+
raise RuntimeError(msg)
|
|
278
|
+
text = match.group()
|
|
279
|
+
self._advance(text)
|
|
280
|
+
return text
|
|
281
|
+
|
|
282
|
+
def pop_pattern(self, pattern):
|
|
283
|
+
regex = re.compile(pattern, re.DOTALL | re.MULTILINE)
|
|
284
|
+
return self.pop_regex(regex)
|
|
285
|
+
|
|
286
|
+
def pop_token(self, regex):
|
|
287
|
+
match = regex.match(self.raw, self.pos)
|
|
288
|
+
if not match:
|
|
289
|
+
return None, self.line, self.column
|
|
290
|
+
else:
|
|
291
|
+
text = match.group()
|
|
292
|
+
line, column = self.line, self.column
|
|
293
|
+
self.pos += len(text)
|
|
294
|
+
if text == "\n":
|
|
295
|
+
self.line += 1
|
|
296
|
+
self.column = 1
|
|
297
|
+
else:
|
|
298
|
+
self.column += len(text)
|
|
299
|
+
return text, line, column
|
|
300
|
+
|
|
301
|
+
class Constraints:
|
|
302
|
+
__slots__ = ("file", "entries")
|
|
303
|
+
|
|
304
|
+
def __init__(self, file):
|
|
305
|
+
self.file = file
|
|
306
|
+
self.entries = {}
|
|
307
|
+
|
|
308
|
+
def log(self, name, value, min_value, max_value):
|
|
309
|
+
if self.file is None or name is None:
|
|
310
|
+
return
|
|
311
|
+
a, b, c, d, e, f = self.entries.get(name, (False, False, min_value, max_value, value, value))
|
|
312
|
+
a |= value == min_value
|
|
313
|
+
b |= value == max_value
|
|
314
|
+
c = min(c, min_value)
|
|
315
|
+
d = max(d, max_value)
|
|
316
|
+
e = min(e, value)
|
|
317
|
+
f = max(f, value)
|
|
318
|
+
self.entries[name] = (a, b, c, d, e, f)
|
|
319
|
+
|
|
320
|
+
def write(self):
|
|
321
|
+
if self.file is None:
|
|
322
|
+
return
|
|
323
|
+
lines = []
|
|
324
|
+
for name, entries in self.entries.items():
|
|
325
|
+
a, b, c, d, e, f = entries
|
|
326
|
+
lines.append(f"{name} {name} {int(a)} {int(b)} {c} {d} {e} {f}")
|
|
327
|
+
self.file.write_text("\n".join(lines))
|
|
328
|
+
|
|
329
|
+
reader = None
|
|
330
|
+
constraints = None
|
|
331
|
+
|
|
332
|
+
def init_lib():
|
|
333
|
+
global reader, constraints
|
|
334
|
+
parser = argparse.ArgumentParser()
|
|
335
|
+
parser.add_argument(
|
|
336
|
+
"--constraints_file",
|
|
337
|
+
dest="constraints_file",
|
|
338
|
+
metavar="constraints_file",
|
|
339
|
+
default=None,
|
|
340
|
+
type=Path,
|
|
341
|
+
required=False,
|
|
342
|
+
help="The file to write constraints to file to use.",
|
|
343
|
+
)
|
|
344
|
+
args, unknown = parser.parse_known_args()
|
|
345
|
+
constraints = Constraints(args.constraints_file)
|
|
346
|
+
|
|
347
|
+
raw = sys.stdin.read()
|
|
348
|
+
reader = _Reader(raw)
|
|
349
|
+
|
|
350
|
+
def finalize_lib():
|
|
351
|
+
constraints.write()
|
|
352
|
+
|
|
353
|
+
# Methods used by Checktestdata
|
|
354
|
+
|
|
355
|
+
def MATCH(arg):
|
|
356
|
+
assert_type("MATCH", arg, String)
|
|
357
|
+
char = reader.peek_char()
|
|
358
|
+
if not char:
|
|
359
|
+
return False
|
|
360
|
+
return Boolean(char in arg.value)
|
|
361
|
+
|
|
362
|
+
def ISEOF():
|
|
363
|
+
return Boolean(not reader.peek_char())
|
|
364
|
+
|
|
365
|
+
def UNIQUE(arg, *args):
|
|
366
|
+
assert_array("UNIQUE", arg)
|
|
367
|
+
for other in args:
|
|
368
|
+
assert_array("UNIQUE", other)
|
|
369
|
+
if arg.entries.keys() != other.entries.keys():
|
|
370
|
+
raise RuntimeError(f"{arg.name} and {other.name} must have the same keys for UNIQUE")
|
|
371
|
+
def make_entry(key):
|
|
372
|
+
return (arg[key], *(other[key] for other in args))
|
|
373
|
+
unique = {make_entry(key) for key in arg.entries.keys()}
|
|
374
|
+
return Boolean(len(unique) == len(arg.entries))
|
|
375
|
+
|
|
376
|
+
def INARRAY(value, array):
|
|
377
|
+
assert isinstance(value, _ValueType)
|
|
378
|
+
assert_array("INARRAY", array)
|
|
379
|
+
return Boolean(array.value_count[value] > 0)
|
|
380
|
+
|
|
381
|
+
def STRLEN(arg):
|
|
382
|
+
assert_type("STRLEN", arg, String)
|
|
383
|
+
return Number(len(arg.value))
|
|
384
|
+
|
|
385
|
+
def SPACE():
|
|
386
|
+
reader.pop_string(" ")
|
|
387
|
+
|
|
388
|
+
def NEWLINE():
|
|
389
|
+
reader.pop_string("\n")
|
|
390
|
+
|
|
391
|
+
def EOF():
|
|
392
|
+
got = reader.peek_char()
|
|
393
|
+
if got:
|
|
394
|
+
msg = f"{reader.line}:{reader.column} got: {msg_text(got)}, but expected {msg_text('')}"
|
|
395
|
+
raise RuntimeError(msg)
|
|
396
|
+
|
|
397
|
+
def INT(min, max, constraint = None):
|
|
398
|
+
assert_type("INT", min, Number)
|
|
399
|
+
assert_type("INT", max, Number)
|
|
400
|
+
text, line, column = reader.pop_token(INTEGER_REGEX)
|
|
401
|
+
if text is None:
|
|
402
|
+
got = reader.peek_until_space()
|
|
403
|
+
raise RuntimeError(f"{line}:{column} expected an integer but got {msg_text(got)}")
|
|
404
|
+
value = int(text)
|
|
405
|
+
if value < min.value or value > max.value:
|
|
406
|
+
raise RuntimeError(f"{line}:{column} integer {text} outside of range [{min.value}, {max.value}]")
|
|
407
|
+
constraints.log(constraint, value, min.value, max.value)
|
|
408
|
+
return Number(value)
|
|
409
|
+
|
|
410
|
+
def FLOAT(min, max, constraint = None, option = FLOAT_REGEX.ANY):
|
|
411
|
+
assert isinstance(option, FLOAT_REGEX)
|
|
412
|
+
assert_type("FLOAT", min, Number)
|
|
413
|
+
assert_type("FLOAT", max, Number)
|
|
414
|
+
text, line, column = reader.pop_token(option.value)
|
|
415
|
+
if text is None:
|
|
416
|
+
got = reader.peek_until_space()
|
|
417
|
+
raise RuntimeError(f"{line}:{column} expected a {option.msg()} but got {msg_text(got)}")
|
|
418
|
+
value = Fraction(text)
|
|
419
|
+
if value < min.value or value > max.value:
|
|
420
|
+
raise RuntimeError(f"{line}:{column} float {text} outside of range [{min.value}, {max.value}]")
|
|
421
|
+
if text.startswith("-") and value == 0:
|
|
422
|
+
raise RuntimeError(f"{line}:{column} float {text} should have no sign")
|
|
423
|
+
constraints.log(constraint, value, min.value, max.value)
|
|
424
|
+
return Number(value)
|
|
425
|
+
|
|
426
|
+
def FLOATP(min, max, mindecimals, maxdecimals, constraint = None, option = FLOAT_REGEX.ANY):
|
|
427
|
+
assert isinstance(option, FLOAT_REGEX)
|
|
428
|
+
assert_type("FLOATP", min, Number)
|
|
429
|
+
assert_type("FLOATP", max, Number)
|
|
430
|
+
assert_type("FLOATP", mindecimals, Number)
|
|
431
|
+
assert_type("FLOATP", maxdecimals, Number)
|
|
432
|
+
if not isinstance(mindecimals.value, int) and mindecimals.value >= 0:
|
|
433
|
+
raise RuntimeError(f"FLOATP(mindecimals) must be a non-negative integer")
|
|
434
|
+
if not isinstance(maxdecimals.value, int) and maxdecimals.value >= 0:
|
|
435
|
+
raise RuntimeError(f"FLOATP(maxdecimals) must be a non-negative integer")
|
|
436
|
+
text, line, column = reader.pop_token(option.value)
|
|
437
|
+
if text is None:
|
|
438
|
+
got = reader.peek_until_space()
|
|
439
|
+
raise RuntimeError(f"{line}:{column} expected a {option.msg()} but got {msg_text(got)}")
|
|
440
|
+
leading, decimals, exponent = FLOAT_PARTS.fullmatch(text).groups()
|
|
441
|
+
decimals = 0 if decimals is None else len(decimals)
|
|
442
|
+
has_exp = exponent is not None
|
|
443
|
+
if decimals < mindecimals.value or decimals > maxdecimals.value:
|
|
444
|
+
raise RuntimeError(f"{line}:{column} float decimals outside of range [{mindecimals.value}, {maxdecimals.value}]")
|
|
445
|
+
if has_exp and (len(leading) != 1 or leading == "0"):
|
|
446
|
+
raise RuntimeError(f"{line}:{column} scientific float should have exactly one non-zero before the decimal dot")
|
|
447
|
+
value = Fraction(text)
|
|
448
|
+
if value < min.value or value > max.value:
|
|
449
|
+
raise RuntimeError(f"{line}:{column} float {text} outside of range [{min.value}, {max.value}]")
|
|
450
|
+
if text.startswith("-") and value == 0:
|
|
451
|
+
raise RuntimeError(f"{line}:{column} float {text} should have no sign")
|
|
452
|
+
constraints.log(constraint, value, min.value, max.value)
|
|
453
|
+
return Number(value)
|
|
454
|
+
|
|
455
|
+
def STRING(arg):
|
|
456
|
+
assert_type("STRING", arg, String)
|
|
457
|
+
reader.pop_string(arg.value)
|
|
458
|
+
|
|
459
|
+
def REGEX(arg):
|
|
460
|
+
assert_type("REGEX", arg, String)
|
|
461
|
+
return String(reader.pop_pattern(arg.value))
|
|
462
|
+
|
|
463
|
+
def ASSERT(arg):
|
|
464
|
+
assert_type("ASSERT", arg, Boolean)
|
|
465
|
+
if not arg.value:
|
|
466
|
+
raise RuntimeError("ASSERT failed")
|
|
467
|
+
|
|
468
|
+
def UNSET(*args):
|
|
469
|
+
for arg in args:
|
|
470
|
+
assert_type("UNSET", arg, VarType)
|
|
471
|
+
arg.reset()
|