umaudemc 0.15.1__py3-none-any.whl → 0.17.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.
- umaudemc/__init__.py +1 -1
- umaudemc/__main__.py +10 -0
- umaudemc/api.py +5 -3
- umaudemc/command/scheck.py +51 -8
- umaudemc/command/sworker.py +15 -8
- umaudemc/distributed.py +22 -17
- umaudemc/pyslang.py +17 -7
- umaudemc/quatex.py +180 -58
- umaudemc/simulators.py +48 -5
- umaudemc/statistical.py +105 -52
- {umaudemc-0.15.1.dist-info → umaudemc-0.17.0.dist-info}/METADATA +1 -1
- {umaudemc-0.15.1.dist-info → umaudemc-0.17.0.dist-info}/RECORD +16 -16
- {umaudemc-0.15.1.dist-info → umaudemc-0.17.0.dist-info}/WHEEL +1 -1
- {umaudemc-0.15.1.dist-info → umaudemc-0.17.0.dist-info}/entry_points.txt +0 -0
- {umaudemc-0.15.1.dist-info → umaudemc-0.17.0.dist-info}/licenses/LICENSE +0 -0
- {umaudemc-0.15.1.dist-info → umaudemc-0.17.0.dist-info}/top_level.txt +0 -0
umaudemc/quatex.py
CHANGED
|
@@ -8,6 +8,19 @@ import os
|
|
|
8
8
|
from . import usermsgs
|
|
9
9
|
|
|
10
10
|
|
|
11
|
+
class QuaTExQuery:
|
|
12
|
+
"""QuaTeX query information"""
|
|
13
|
+
|
|
14
|
+
def __init__(self, fname, line, column, expr, parameters, delta):
|
|
15
|
+
self.filename = fname # file cointing the query
|
|
16
|
+
self.line = line # query location
|
|
17
|
+
self.column = column
|
|
18
|
+
self.parameters = parameters # parameters as (variable, start, step, end)
|
|
19
|
+
|
|
20
|
+
self.expr = expr # query expression (only needed for compilation)
|
|
21
|
+
self.delta = delta # delta value
|
|
22
|
+
|
|
23
|
+
|
|
11
24
|
class QuaTExProgram:
|
|
12
25
|
"""Compiled QuaTEx program"""
|
|
13
26
|
|
|
@@ -23,7 +36,7 @@ class QuaTExProgram:
|
|
|
23
36
|
self.nqueries = len(slots) - ndefs
|
|
24
37
|
|
|
25
38
|
# Query information (file name, line, column, and parameters)
|
|
26
|
-
self.
|
|
39
|
+
self.queries = qinfo
|
|
27
40
|
|
|
28
41
|
|
|
29
42
|
class QuaTExLexer:
|
|
@@ -144,6 +157,11 @@ class QuaTExLexer:
|
|
|
144
157
|
self.sline = self.line
|
|
145
158
|
self.scolumn = self.column
|
|
146
159
|
|
|
160
|
+
@staticmethod
|
|
161
|
+
def _is_name(c):
|
|
162
|
+
"""Whether the character is allowed in a name"""
|
|
163
|
+
return c.isalnum() or c == '$'
|
|
164
|
+
|
|
147
165
|
def get_token(self):
|
|
148
166
|
"""Get the next token from the stream"""
|
|
149
167
|
|
|
@@ -166,9 +184,10 @@ class QuaTExLexer:
|
|
|
166
184
|
self.ltype = self.LT_STRING
|
|
167
185
|
self._capture_string()
|
|
168
186
|
|
|
169
|
-
|
|
187
|
+
# Names cannot start with a number
|
|
188
|
+
elif c.isalpha() or c == '$':
|
|
170
189
|
self.ltype = self.LT_NAME
|
|
171
|
-
self._capture(
|
|
190
|
+
self._capture(self._is_name)
|
|
172
191
|
|
|
173
192
|
elif c.isdecimal():
|
|
174
193
|
self.ltype = self.LT_NUMBER
|
|
@@ -206,9 +225,9 @@ class QuaTExLexer:
|
|
|
206
225
|
class QuaTExParser:
|
|
207
226
|
"""Parser for QuaTEx"""
|
|
208
227
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
228
|
+
PS_IF_COND = 0 # condition
|
|
229
|
+
PS_IF_THEN = 1 # true branch
|
|
230
|
+
PS_IF_ELSE = 2 # negative branch
|
|
212
231
|
PS_PAREN = 3 # parenthesis
|
|
213
232
|
PS_ARITH = 4 # completing an arithmetic expression
|
|
214
233
|
PS_CALLARGS = 5 # call arguments
|
|
@@ -218,12 +237,13 @@ class QuaTExParser:
|
|
|
218
237
|
BINOPS_PREC = (4, 4, 3, 3, 3, 11, 12, 7, 7, 6, 6, 6, 6)
|
|
219
238
|
BINOPS_AST = (ast.Add, ast.Sub, ast.Mult, ast.Div, ast.Mod, ast.And, ast.Or, ast.Eq,
|
|
220
239
|
ast.NotEq, ast.Lt, ast.LtE, ast.Gt, ast.GtE)
|
|
240
|
+
# Kind of binary operator (0 = BinOp, 1 = BoolOp, 2 = Compare)
|
|
221
241
|
BINOPS_CMP = (0, ) * 5 + (1, ) * 2 + (2, ) * 6
|
|
222
242
|
# Unary operator and its precedence (as in C)
|
|
223
243
|
UNARY_OPS = ('!', )
|
|
224
244
|
UNARY_AST = (ast.Not, )
|
|
225
245
|
|
|
226
|
-
def __init__(self, source, filename='<stdin>', legacy=False):
|
|
246
|
+
def __init__(self, source, filename='<stdin>', legacy=False, constants=None):
|
|
227
247
|
# Filename is only used for diagnostics
|
|
228
248
|
self.lexer = QuaTExLexer(source, filename)
|
|
229
249
|
# PMaude legacy syntax
|
|
@@ -233,11 +253,14 @@ class QuaTExParser:
|
|
|
233
253
|
self.pending_lexers = []
|
|
234
254
|
self.seen_files = set() if filename.startswith('<') else {os.path.realpath(filename)}
|
|
235
255
|
|
|
236
|
-
#
|
|
237
|
-
self.fvars = []
|
|
238
|
-
# Whether the variables that may occur
|
|
239
|
-
# in an expression are known
|
|
256
|
+
# Whether the variables that may occur in an expression are known
|
|
240
257
|
self.known_vars = True
|
|
258
|
+
# Parameters of the current function if self.known_vars
|
|
259
|
+
# or variables found in the last processed expression otherwise
|
|
260
|
+
self.fvars = []
|
|
261
|
+
# Constants defined outside
|
|
262
|
+
self.constants = {} if constants is None else constants
|
|
263
|
+
self.pending_constants = []
|
|
241
264
|
|
|
242
265
|
# State stack for parsing expressions
|
|
243
266
|
self.stack = []
|
|
@@ -246,7 +269,7 @@ class QuaTExParser:
|
|
|
246
269
|
# Whether parsing errors have been encountered
|
|
247
270
|
self.ok = True
|
|
248
271
|
|
|
249
|
-
# Compilation slot indices for each
|
|
272
|
+
# Compilation slot indices for each element
|
|
250
273
|
self.fslots = {}
|
|
251
274
|
self.calls = []
|
|
252
275
|
self.observations = []
|
|
@@ -277,13 +300,29 @@ class QuaTExParser:
|
|
|
277
300
|
|
|
278
301
|
return True
|
|
279
302
|
|
|
303
|
+
def _expect_any(self, *options):
|
|
304
|
+
"""Check whether any of the given strings is the next token"""
|
|
305
|
+
|
|
306
|
+
atoken = self.lexer.get_token()
|
|
307
|
+
options_str = ', '.join(f'"{opt}"' for opt in options)
|
|
308
|
+
|
|
309
|
+
if atoken is None:
|
|
310
|
+
self._eprint(f'unexpected end of file where any of {options_str} is required.')
|
|
311
|
+
return None
|
|
312
|
+
|
|
313
|
+
if atoken not in options:
|
|
314
|
+
self._eprint(f'unexpected token "{atoken}" where any of {options_str} is required.')
|
|
315
|
+
return None
|
|
316
|
+
|
|
317
|
+
return atoken
|
|
318
|
+
|
|
280
319
|
def _in_state(self, state):
|
|
281
320
|
"""Check whether the parser is in the given state"""
|
|
282
321
|
|
|
283
322
|
return self.stack and self.stack[-1] == state
|
|
284
323
|
|
|
285
324
|
def _next_token(self):
|
|
286
|
-
"""Get the next token
|
|
325
|
+
"""Get the next token dealing with file exhaustion"""
|
|
287
326
|
|
|
288
327
|
token = self.lexer.get_token()
|
|
289
328
|
|
|
@@ -325,40 +364,89 @@ class QuaTExParser:
|
|
|
325
364
|
self._eprint(f'unexpected token "{var_name}" where a variable name is required.')
|
|
326
365
|
return (None, ) * 3
|
|
327
366
|
|
|
367
|
+
if not self._expect(','):
|
|
368
|
+
return (None, ) * 3
|
|
369
|
+
|
|
328
370
|
# Parameter specification (name, initial value, step, last value)
|
|
329
371
|
spec = [var_name]
|
|
330
372
|
|
|
331
|
-
#
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
373
|
+
# Make a copy of the expression variables, since we continue in
|
|
374
|
+
# known variables mode with no variables allowed
|
|
375
|
+
expr_vars = tuple(self.fvars)
|
|
376
|
+
self.fvars.clear()
|
|
335
377
|
|
|
336
|
-
|
|
378
|
+
# Parse the initial value, step, and last value
|
|
379
|
+
for k in range(3):
|
|
380
|
+
expr = self._parse_constexpr(',' if k < 2 else ')')
|
|
337
381
|
|
|
338
|
-
if
|
|
339
|
-
self._eprint(f'unexpected token "{token}" where a number is required.')
|
|
382
|
+
if expr is None:
|
|
340
383
|
return (None, ) * 3
|
|
341
384
|
|
|
342
|
-
spec.append(float(
|
|
343
|
-
|
|
344
|
-
# Check the closing parenthesis
|
|
345
|
-
if not self._expect(')'):
|
|
346
|
-
return (None, ) * 3
|
|
385
|
+
spec.append(float(expr))
|
|
347
386
|
|
|
348
387
|
# Check whether the variables in the expressions are the parameter
|
|
349
|
-
for var, line, column in
|
|
388
|
+
for var, line, column in expr_vars:
|
|
350
389
|
if var != var_name:
|
|
351
390
|
self._eprint(f'unknown variable "{var}".', line=line, column=column)
|
|
352
391
|
self.ok = False
|
|
353
392
|
|
|
393
|
+
return tuple(spec)
|
|
394
|
+
|
|
395
|
+
def _parse_delta(self, parameter):
|
|
396
|
+
"""Parse the delta annotation"""
|
|
397
|
+
|
|
398
|
+
if not self._expect('delta', '='):
|
|
399
|
+
return None
|
|
400
|
+
|
|
401
|
+
# Only the parameter can appear as free variable in the expression
|
|
354
402
|
self.fvars.clear()
|
|
355
403
|
|
|
356
|
-
|
|
404
|
+
if parameter:
|
|
405
|
+
self.fvars.append(parameter[0])
|
|
406
|
+
|
|
407
|
+
if (delta := self._parse_expr(';', constexpr=True)) is None:
|
|
408
|
+
return None
|
|
409
|
+
|
|
410
|
+
# Prepare delta as a function on the parameter (if any)
|
|
411
|
+
args = [ast.arg(parameter[0])] if parameter else []
|
|
412
|
+
|
|
413
|
+
delta_fn = ast.Expression(ast.Lambda(ast.arguments(args=args), delta))
|
|
414
|
+
ast.fix_missing_locations(delta_fn)
|
|
357
415
|
|
|
358
|
-
|
|
416
|
+
return eval(compile(delta_fn, '<delta>', 'eval'), {}, {})
|
|
417
|
+
|
|
418
|
+
def _parse_constexpr(self, end_token):
|
|
419
|
+
"""Parse a constant expression as a number"""
|
|
420
|
+
|
|
421
|
+
# Soft errors, like unbound variables, are hard here because
|
|
422
|
+
# we run the code to obtain a value for the expression
|
|
423
|
+
old_ok, self.ok = self.ok, True
|
|
424
|
+
|
|
425
|
+
if (expr := self._parse_expr(end_token, constexpr=True)) is None:
|
|
426
|
+
return None
|
|
427
|
+
|
|
428
|
+
elif not self.ok:
|
|
429
|
+
return 0.0 # dummy value to keep running
|
|
430
|
+
|
|
431
|
+
self.ok = old_ok
|
|
432
|
+
|
|
433
|
+
# Evaluate the expression
|
|
434
|
+
expr = ast.Expression(expr)
|
|
435
|
+
ast.fix_missing_locations(expr)
|
|
436
|
+
|
|
437
|
+
result = eval(compile(expr, self.lexer.filename, 'eval'), {}, {})
|
|
438
|
+
|
|
439
|
+
# Check whether it is a number
|
|
440
|
+
if not isinstance(result, (int, float)):
|
|
441
|
+
self._eprint(f'"{result}" obtained while a number is expected.')
|
|
442
|
+
return None
|
|
443
|
+
|
|
444
|
+
return result
|
|
445
|
+
|
|
446
|
+
def _parse_expr(self, end_token, constexpr=False):
|
|
359
447
|
"""Parse an expression"""
|
|
360
448
|
|
|
361
|
-
# Current expression
|
|
449
|
+
# Current expression (as a Python AST node)
|
|
362
450
|
current = None
|
|
363
451
|
# Number of nested conditions in the current position
|
|
364
452
|
inside_cond = 0
|
|
@@ -393,13 +481,13 @@ class QuaTExParser:
|
|
|
393
481
|
self._eprint('misplaced "if" keyword.')
|
|
394
482
|
return None
|
|
395
483
|
|
|
396
|
-
self.stack.append(self.
|
|
484
|
+
self.stack.append(self.PS_IF_COND)
|
|
397
485
|
arg_stack.append([])
|
|
398
486
|
inside_cond += 1
|
|
399
487
|
|
|
400
488
|
elif token == 'then':
|
|
401
|
-
if current and self._in_state(self.
|
|
402
|
-
self.stack[-1] = self.
|
|
489
|
+
if current and self._in_state(self.PS_IF_COND):
|
|
490
|
+
self.stack[-1] = self.PS_IF_THEN
|
|
403
491
|
arg_stack[-1].append(current)
|
|
404
492
|
current = None
|
|
405
493
|
inside_cond -= 1
|
|
@@ -408,8 +496,8 @@ class QuaTExParser:
|
|
|
408
496
|
return None
|
|
409
497
|
|
|
410
498
|
elif token == 'else':
|
|
411
|
-
if current and self._in_state(self.
|
|
412
|
-
self.stack[-1] = self.
|
|
499
|
+
if current and self._in_state(self.PS_IF_THEN):
|
|
500
|
+
self.stack[-1] = self.PS_IF_ELSE
|
|
413
501
|
arg_stack[-1].append(current)
|
|
414
502
|
current = None
|
|
415
503
|
else:
|
|
@@ -417,7 +505,7 @@ class QuaTExParser:
|
|
|
417
505
|
return None
|
|
418
506
|
|
|
419
507
|
elif token == 'fi':
|
|
420
|
-
if current and self._in_state(self.
|
|
508
|
+
if current and self._in_state(self.PS_IF_ELSE):
|
|
421
509
|
arg_stack[-1].append(current)
|
|
422
510
|
current = ast.IfExp(*arg_stack[-1])
|
|
423
511
|
arg_stack.pop()
|
|
@@ -435,6 +523,10 @@ class QuaTExParser:
|
|
|
435
523
|
self._eprint('the next operator # cannot be used in conditions or call arguments.')
|
|
436
524
|
self.ok = False
|
|
437
525
|
|
|
526
|
+
elif constexpr:
|
|
527
|
+
self._eprint('the next operator # cannot be used in constant contexts.')
|
|
528
|
+
self.ok = False
|
|
529
|
+
|
|
438
530
|
# A function call should follow
|
|
439
531
|
token = self.lexer.get_token()
|
|
440
532
|
line, column = self.lexer.sline, self.lexer.scolumn
|
|
@@ -448,6 +540,14 @@ class QuaTExParser:
|
|
|
448
540
|
inside_next = True
|
|
449
541
|
call_name, call_line, call_column = token, line, column
|
|
450
542
|
|
|
543
|
+
# discard is a soft keyword
|
|
544
|
+
elif token == 'discard' and not (inside_cond or call_name or constexpr):
|
|
545
|
+
if current:
|
|
546
|
+
self._eprint('misplaced discard keyword.')
|
|
547
|
+
return None
|
|
548
|
+
|
|
549
|
+
current = ast.Constant(None)
|
|
550
|
+
|
|
451
551
|
elif token == ',':
|
|
452
552
|
if current and self._in_state(self.PS_CALLARGS):
|
|
453
553
|
arg_stack[-1].append(current)
|
|
@@ -477,10 +577,15 @@ class QuaTExParser:
|
|
|
477
577
|
self._eprint(f'argument missing after a comma in a call to "{call_name}".')
|
|
478
578
|
self.ok = False
|
|
479
579
|
|
|
580
|
+
# Call are internally represented as tuples that indicate whether
|
|
581
|
+
# the call is preceded by next (i.e. to be evaluated after a step)
|
|
582
|
+
# and which code slot contains the callee definition
|
|
480
583
|
slot = self.fslots.setdefault(call_name, len(self.fslots))
|
|
481
584
|
current = ast.Tuple([ast.Constant(inside_next), ast.Constant(slot), *args], ast.Load())
|
|
482
|
-
|
|
483
|
-
|
|
585
|
+
# We keep the source location for error reporting (using the attributes
|
|
586
|
+
# lineno and col_offset of the AST node only here is not possible)
|
|
587
|
+
current.custom_loc = dict(fname=self.lexer.filename, line=call_line, column=call_column)
|
|
588
|
+
self.calls.append((call_name, self.lexer.filename, call_line, call_column, len(args)))
|
|
484
589
|
inside_next = False
|
|
485
590
|
call_name = None
|
|
486
591
|
|
|
@@ -535,6 +640,10 @@ class QuaTExParser:
|
|
|
535
640
|
self._eprint(f'"{token}" is called in a condition or call argument, but this is not allowed.')
|
|
536
641
|
return None
|
|
537
642
|
|
|
643
|
+
if constexpr:
|
|
644
|
+
self._eprint(f'"{token}" is called in a constant context, but calls are not allowed.')
|
|
645
|
+
return None
|
|
646
|
+
|
|
538
647
|
self.stack.append(self.PS_CALLARGS)
|
|
539
648
|
arg_stack.append([])
|
|
540
649
|
call_name = token
|
|
@@ -542,14 +651,19 @@ class QuaTExParser:
|
|
|
542
651
|
|
|
543
652
|
# Simply a variable
|
|
544
653
|
else:
|
|
545
|
-
|
|
654
|
+
current = ast.Name(token, ast.Load())
|
|
655
|
+
|
|
656
|
+
# Constants (from the command line) start with $, but variables can also
|
|
657
|
+
if token.startswith('$') and (value := self.constants.get(token[1:])) is not None:
|
|
658
|
+
current = ast.Constant(value)
|
|
659
|
+
|
|
660
|
+
elif not self.known_vars:
|
|
546
661
|
self.fvars.append((token, line, column))
|
|
547
662
|
|
|
548
663
|
elif token not in self.fvars:
|
|
549
664
|
self._eprint(f'unknown variable "{token}".', line=line, column=column)
|
|
550
665
|
self.ok = False
|
|
551
666
|
|
|
552
|
-
current = ast.Name(token, ast.Load())
|
|
553
667
|
|
|
554
668
|
# We continue with the peeked token
|
|
555
669
|
token = next_token
|
|
@@ -674,22 +788,29 @@ class QuaTExParser:
|
|
|
674
788
|
if parameter:
|
|
675
789
|
self.known_vars = False
|
|
676
790
|
|
|
677
|
-
expr
|
|
791
|
+
if not (expr := self._parse_expr(']')):
|
|
792
|
+
return False
|
|
678
793
|
|
|
679
794
|
self.known_vars = True
|
|
680
795
|
|
|
681
796
|
# Parse parameter specification in parametric queries
|
|
682
797
|
parameter = self._parse_parameter() if parameter else None
|
|
798
|
+
delta = None
|
|
683
799
|
|
|
684
|
-
|
|
800
|
+
# Check whether there is a "with delta = <number>" prefix
|
|
801
|
+
if (token := self._expect_any('with', ';')) is None:
|
|
685
802
|
return False
|
|
686
803
|
|
|
804
|
+
if token == 'with':
|
|
805
|
+
if (delta := self._parse_delta(parameter)) is None:
|
|
806
|
+
return False
|
|
807
|
+
|
|
687
808
|
# Ignore parameterized expressions with empty range
|
|
688
809
|
if parameter and parameter[1] > parameter[3]:
|
|
689
810
|
usermsgs.print_warning_loc(self.lexer.filename, line, column,
|
|
690
811
|
'ignoring parametric query with empty range.')
|
|
691
812
|
else:
|
|
692
|
-
self.queries.append((self.lexer.filename, line, column, expr, parameter))
|
|
813
|
+
self.queries.append(QuaTExQuery(self.lexer.filename, line, column, expr, parameter, delta))
|
|
693
814
|
|
|
694
815
|
# Function definition -- <name> ( <args> ) = <expr> ;
|
|
695
816
|
else:
|
|
@@ -724,7 +845,7 @@ class QuaTExParser:
|
|
|
724
845
|
if not self._expect('='):
|
|
725
846
|
return False
|
|
726
847
|
|
|
727
|
-
expr = self._parse_expr(';'
|
|
848
|
+
expr = self._parse_expr(';')
|
|
728
849
|
|
|
729
850
|
if not expr:
|
|
730
851
|
return False
|
|
@@ -743,8 +864,7 @@ class QuaTExParser:
|
|
|
743
864
|
expr, tail_pos = pending.pop()
|
|
744
865
|
|
|
745
866
|
if isinstance(expr, ast.Tuple) and not tail_pos:
|
|
746
|
-
self._eprint('non-tail calls are not allowed.',
|
|
747
|
-
line=expr.custom_loc[0], column=expr.custom_loc[1])
|
|
867
|
+
self._eprint('non-tail calls are not allowed.', **expr.custom_loc)
|
|
748
868
|
return False
|
|
749
869
|
|
|
750
870
|
elif isinstance(expr, ast.UnaryOp):
|
|
@@ -779,17 +899,17 @@ class QuaTExParser:
|
|
|
779
899
|
arities[name] = len(args)
|
|
780
900
|
|
|
781
901
|
# Check whether all calls are well-defined
|
|
782
|
-
for name, line, column, arity in self.calls:
|
|
902
|
+
for name, fname, line, column, arity in self.calls:
|
|
783
903
|
def_arity = arities.get(name)
|
|
784
904
|
|
|
785
905
|
if def_arity is None:
|
|
786
906
|
self._eprint(f'call to undefined function "{name}".',
|
|
787
|
-
line=line, column=column)
|
|
907
|
+
line=line, column=column, fname=fname)
|
|
788
908
|
ok = False
|
|
789
909
|
|
|
790
910
|
elif arity != def_arity:
|
|
791
911
|
self._eprint(f'wrong number of arguments in a call to "{name}" ({arity} given, but {def_arity} expected).',
|
|
792
|
-
line=line, column=column)
|
|
912
|
+
line=line, column=column, fname=fname)
|
|
793
913
|
ok = False
|
|
794
914
|
|
|
795
915
|
# Check all calls are tail in expression
|
|
@@ -797,8 +917,8 @@ class QuaTExParser:
|
|
|
797
917
|
if not self._check_tail(expr):
|
|
798
918
|
ok = False
|
|
799
919
|
|
|
800
|
-
for
|
|
801
|
-
if not self._check_tail(expr):
|
|
920
|
+
for query in self.queries:
|
|
921
|
+
if not self._check_tail(query.expr):
|
|
802
922
|
ok = False
|
|
803
923
|
|
|
804
924
|
if not ok:
|
|
@@ -828,28 +948,30 @@ class QuaTExParser:
|
|
|
828
948
|
line=line, column=column)
|
|
829
949
|
ok = False
|
|
830
950
|
|
|
831
|
-
for k,
|
|
951
|
+
for k, query in enumerate(self.queries):
|
|
832
952
|
try:
|
|
833
|
-
expr = ast.Expression(expr)
|
|
953
|
+
expr = ast.Expression(query.expr)
|
|
834
954
|
ast.fix_missing_locations(expr)
|
|
835
|
-
slots[used_defs + k] = compile(expr, filename=f'query{
|
|
955
|
+
slots[used_defs + k] = compile(expr, filename=f'query{query.filename}:{query.line}:{query.column}', mode='eval')
|
|
836
956
|
|
|
837
957
|
except TypeError:
|
|
838
958
|
self._eprint('this query cannot cannot be compiled.',
|
|
839
|
-
line=line, column=column, fname=fname)
|
|
959
|
+
line=query.line, column=query.column, fname=query.fname)
|
|
840
960
|
ok = False
|
|
841
961
|
|
|
962
|
+
# No longer needed
|
|
963
|
+
query.expr = None
|
|
964
|
+
|
|
842
965
|
if not ok:
|
|
843
966
|
return None
|
|
844
967
|
|
|
845
|
-
return QuaTExProgram(slots, varnames, len(self.fslots),
|
|
846
|
-
tuple((fname, line, column, params) for fname, line, column, _, params in self.queries))
|
|
968
|
+
return QuaTExProgram(slots, varnames, len(self.fslots), self.queries)
|
|
847
969
|
|
|
848
970
|
|
|
849
|
-
def parse_quatex(input_file, filename='<string>', legacy=False):
|
|
971
|
+
def parse_quatex(input_file, filename='<string>', legacy=False, constants=None):
|
|
850
972
|
"""Parse a QuaTEx formula"""
|
|
851
973
|
|
|
852
974
|
# Load, parse, and compile the QuaTEx file
|
|
853
|
-
parser = QuaTExParser(input_file, filename=filename, legacy=legacy)
|
|
975
|
+
parser = QuaTExParser(input_file, filename=filename, legacy=legacy, constants=constants)
|
|
854
976
|
|
|
855
977
|
return parser.parse(), parser.seen_files
|
umaudemc/simulators.py
CHANGED
|
@@ -27,7 +27,11 @@ def parse_hole_term(module, term_str):
|
|
|
27
27
|
|
|
28
28
|
# Collect all variables in the term
|
|
29
29
|
varset = set()
|
|
30
|
-
|
|
30
|
+
|
|
31
|
+
if term.isVariable():
|
|
32
|
+
varset.add(term)
|
|
33
|
+
else:
|
|
34
|
+
collect_vars(term, varset)
|
|
31
35
|
|
|
32
36
|
if len(varset) > 1:
|
|
33
37
|
usermsgs.print_warning('The observation "{message}" '
|
|
@@ -105,6 +109,42 @@ class StrategyStepSimulator(BaseSimulator):
|
|
|
105
109
|
self.step += 1
|
|
106
110
|
|
|
107
111
|
|
|
112
|
+
class RuleStepSimulator(BaseSimulator):
|
|
113
|
+
"""Simulator where rule application (potentially using random) is the step"""
|
|
114
|
+
|
|
115
|
+
def __init__(self, initial):
|
|
116
|
+
super().__init__(initial)
|
|
117
|
+
|
|
118
|
+
# See the PMaude simulator to an explanation for why we need that
|
|
119
|
+
self.random = None
|
|
120
|
+
|
|
121
|
+
if nat_kind := self.module.findSort('Nat').kind():
|
|
122
|
+
if random := self.module.findSymbol('random', (nat_kind,), nat_kind):
|
|
123
|
+
self.random = random(self.module.parseTerm('1', nat_kind))
|
|
124
|
+
|
|
125
|
+
def restart(self):
|
|
126
|
+
"""Restart simulator"""
|
|
127
|
+
|
|
128
|
+
super().restart()
|
|
129
|
+
|
|
130
|
+
# PMaude uses Maude's random symbol, which is memoryless
|
|
131
|
+
# and deterministic for a fixed seed, so we need a new seed
|
|
132
|
+
if self.random:
|
|
133
|
+
self.random.copy().reduce()
|
|
134
|
+
maude.setRandomSeed(random.getrandbits(31))
|
|
135
|
+
|
|
136
|
+
def next_step(self):
|
|
137
|
+
"""Perform a step of the simulation"""
|
|
138
|
+
|
|
139
|
+
# Application of any executable rule
|
|
140
|
+
# (assumming it is deterministic)
|
|
141
|
+
next_state, *_ = next(self.state.apply(None), (None,))
|
|
142
|
+
|
|
143
|
+
if next_state is not None:
|
|
144
|
+
self.state = next_state
|
|
145
|
+
self.step += 1
|
|
146
|
+
|
|
147
|
+
|
|
108
148
|
def all_children(graph, state):
|
|
109
149
|
"""All children of a state in a graph"""
|
|
110
150
|
|
|
@@ -275,8 +315,8 @@ class PMaudeSimulator(BaseSimulator):
|
|
|
275
315
|
self.state.rewrite()
|
|
276
316
|
|
|
277
317
|
# Try to find Maude's random symbol for calculating random(1). If the
|
|
278
|
-
# PMaude specification only reduces random(0), even after resetting
|
|
279
|
-
#
|
|
318
|
+
# PMaude specification only reduces random(0), even after resetting the
|
|
319
|
+
# random seed for a new simulation, it will take the same cached value
|
|
280
320
|
self.random = self.module.findSymbol('random', (nat_kind,), nat_kind)
|
|
281
321
|
|
|
282
322
|
if self.random:
|
|
@@ -469,12 +509,15 @@ def get_simulator(method, data):
|
|
|
469
509
|
method = 'step' if data.strategy else 'uniform'
|
|
470
510
|
|
|
471
511
|
# Check whether a strategy is provided for methods that require it
|
|
472
|
-
if not data.strategy and method in ('
|
|
512
|
+
if not data.strategy and method in ('strategy-fast', 'strategy'):
|
|
473
513
|
usermsgs.print_error(f'No strategy is provided for the {method} assignment method.')
|
|
474
514
|
return None
|
|
475
515
|
|
|
476
516
|
if method == 'step':
|
|
477
|
-
|
|
517
|
+
if data.strategy:
|
|
518
|
+
return StrategyStepSimulator(data.term, data.strategy)
|
|
519
|
+
else:
|
|
520
|
+
return RuleStepSimulator(data.term)
|
|
478
521
|
|
|
479
522
|
if method == 'strategy-fast':
|
|
480
523
|
return StrategyPathSimulator(data.module, data.term, data.strategy)
|