Coverage for asteval/astutils.py: 92%
357 statements
« prev ^ index » next coverage.py v7.4.4, created at 2024-12-05 11:00 +0000
« prev ^ index » next coverage.py v7.4.4, created at 2024-12-05 11:00 +0000
1"""
2utility functions for asteval
4 Matthew Newville <newville@cars.uchicago.edu>,
5 The University of Chicago
6"""
7import ast
8import io
9import math
10import numbers
11import re
12from sys import exc_info
13from tokenize import ENCODING as tk_ENCODING
14from tokenize import NAME as tk_NAME
15from tokenize import tokenize as generate_tokens
17builtins = __builtins__
18if not isinstance(builtins, dict):
19 builtins = builtins.__dict__
21HAS_NUMPY = False
22try:
23 import numpy
24 numpy_version = numpy.version.version.split('.', 2)
25 HAS_NUMPY = True
26except ImportError:
27 numpy = None
29HAS_NUMPY_FINANCIAL = False
30try:
31 import numpy_financial
32 HAS_NUMPY_FINANCIAL = True
33except ImportError:
34 pass
38MAX_EXPONENT = 10000
39MAX_STR_LEN = 2 << 17 # 256KiB
40MAX_SHIFT = 1000
41MAX_OPEN_BUFFER = 2 << 17
43RESERVED_WORDS = ('False', 'None', 'True', 'and', 'as', 'assert',
44 'async', 'await', 'break', 'class', 'continue', 'def',
45 'del', 'elif', 'else', 'except', 'finally', 'for',
46 'from', 'global', 'if', 'import', 'in', 'is',
47 'lambda', 'nonlocal', 'not', 'or', 'pass', 'raise',
48 'return', 'try', 'while', 'with', 'yield', 'exec',
49 'eval', 'execfile', '__import__', '__package__',
50 '__fstring__')
52NAME_MATCH = re.compile(r"[a-zA-Z_][a-zA-Z0-9_]*$").match
54# unsafe attributes for all objects:
55UNSAFE_ATTRS = ('__subclasses__', '__bases__', '__globals__', '__code__',
56 '__reduce__', '__reduce_ex__', '__mro__',
57 '__closure__', '__func__', '__self__', '__module__',
58 '__dict__', '__class__', '__call__', '__get__',
59 '__getattribute__', '__subclasshook__', '__new__',
60 '__init__', 'func_globals', 'func_code', 'func_closure',
61 'im_class', 'im_func', 'im_self', 'gi_code', 'gi_frame',
62 'f_locals', '__asteval__')
64# unsafe attributes for particular objects, by type
65UNSAFE_ATTRS_DTYPES = {str: ('format', 'format_map')}
68# inherit these from python's __builtins__
69FROM_PY = ('ArithmeticError', 'AssertionError', 'AttributeError',
70 'BaseException', 'BufferError', 'BytesWarning',
71 'DeprecationWarning', 'EOFError', 'EnvironmentError',
72 'Exception', 'False', 'FloatingPointError', 'GeneratorExit',
73 'IOError', 'ImportError', 'ImportWarning', 'IndentationError',
74 'IndexError', 'KeyError', 'KeyboardInterrupt', 'LookupError',
75 'MemoryError', 'NameError', 'None',
76 'NotImplementedError', 'OSError', 'OverflowError',
77 'ReferenceError', 'RuntimeError', 'RuntimeWarning',
78 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError',
79 'SystemExit', 'True', 'TypeError', 'UnboundLocalError',
80 'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError',
81 'UnicodeTranslateError', 'UnicodeWarning', 'ValueError',
82 'Warning', 'ZeroDivisionError', 'abs', 'all', 'any', 'bin',
83 'bool', 'bytearray', 'bytes', 'chr', 'complex', 'dict', 'dir',
84 'divmod', 'enumerate', 'filter', 'float', 'format', 'frozenset',
85 'hash', 'hex', 'id', 'int', 'isinstance', 'len', 'list', 'map',
86 'max', 'min', 'oct', 'ord', 'pow', 'range', 'repr',
87 'reversed', 'round', 'set', 'slice', 'sorted', 'str', 'sum',
88 'tuple', 'zip')
90BUILTINS_TABLE = {sym: builtins[sym] for sym in FROM_PY if sym in builtins}
92# inherit these from python's math
93FROM_MATH = ('acos', 'acosh', 'asin', 'asinh', 'atan', 'atan2', 'atanh',
94 'ceil', 'copysign', 'cos', 'cosh', 'degrees', 'e', 'exp',
95 'fabs', 'factorial', 'floor', 'fmod', 'frexp', 'fsum',
96 'hypot', 'isinf', 'isnan', 'ldexp', 'log', 'log10', 'log1p',
97 'modf', 'pi', 'pow', 'radians', 'sin', 'sinh', 'sqrt', 'tan',
98 'tanh', 'trunc')
100MATH_TABLE = {sym: getattr(math, sym) for sym in FROM_MATH if hasattr(math, sym)}
102FROM_NUMPY = ('abs', 'add', 'all', 'amax', 'amin', 'angle', 'any', 'append',
103 'arange', 'arccos', 'arccosh', 'arcsin', 'arcsinh', 'arctan', 'arctan2',
104 'arctanh', 'argmax', 'argmin', 'argsort', 'argwhere', 'around', 'array',
105 'asarray', 'atleast_1d', 'atleast_2d', 'atleast_3d', 'average', 'bartlett',
106 'bitwise_and', 'bitwise_not', 'bitwise_or', 'bitwise_xor', 'blackman',
107 'broadcast', 'ceil', 'choose', 'clip', 'column_stack', 'common_type',
108 'complex128', 'compress', 'concatenate', 'conjugate', 'convolve',
109 'copysign', 'corrcoef', 'correlate', 'cos', 'cosh', 'cov', 'cross',
110 'cumprod', 'cumsum', 'datetime_data', 'deg2rad', 'degrees', 'delete',
111 'diag', 'diag_indices', 'diag_indices_from', 'diagflat', 'diagonal',
112 'diff', 'digitize', 'divide', 'dot', 'dsplit', 'dstack', 'dtype', 'e',
113 'ediff1d', 'empty', 'empty_like', 'equal', 'exp', 'exp2', 'expand_dims',
114 'expm1', 'extract', 'eye', 'fabs', 'fill_diagonal', 'finfo', 'fix',
115 'flatiter', 'flatnonzero', 'fliplr', 'flipud', 'float64', 'floor',
116 'floor_divide', 'fmax', 'fmin', 'fmod', 'format_parser', 'frexp',
117 'frombuffer', 'fromfile', 'fromfunction', 'fromiter', 'frompyfunc',
118 'fromregex', 'fromstring', 'genfromtxt', 'getbufsize', 'geterr',
119 'gradient', 'greater', 'greater_equal', 'hamming', 'hanning', 'histogram',
120 'histogram2d', 'histogramdd', 'hsplit', 'hstack', 'hypot', 'i0',
121 'identity', 'iinfo', 'imag', 'indices', 'inexact', 'inf', 'info', 'inner',
122 'insert', 'int32', 'integer', 'interp', 'intersect1d', 'invert',
123 'iscomplex', 'iscomplexobj', 'isfinite', 'isinf', 'isnan', 'isneginf',
124 'isposinf', 'isreal', 'isrealobj', 'isscalar', 'iterable', 'kaiser',
125 'kron', 'ldexp', 'left_shift', 'less', 'less_equal', 'linalg', 'linspace',
126 'little_endian', 'loadtxt', 'log', 'log10', 'log1p', 'log2', 'logaddexp',
127 'logaddexp2', 'logical_and', 'logical_not', 'logical_or', 'logical_xor',
128 'logspace', 'longdouble', 'longlong', 'mask_indices', 'matrix', 'maximum',
129 'may_share_memory', 'mean', 'median', 'memmap', 'meshgrid', 'minimum',
130 'mintypecode', 'mod', 'modf', 'msort', 'multiply', 'nan', 'nan_to_num',
131 'nanargmax', 'nanargmin', 'nanmax', 'nanmin', 'nansum', 'ndarray',
132 'ndenumerate', 'ndim', 'ndindex', 'negative', 'nextafter', 'nonzero',
133 'not_equal', 'number', 'ones', 'ones_like', 'outer', 'packbits',
134 'percentile', 'pi', 'piecewise', 'place', 'poly', 'poly1d', 'polyadd',
135 'polyder', 'polydiv', 'polyint', 'polymul', 'polysub', 'polyval', 'power',
136 'prod', 'ptp', 'put', 'putmask', 'rad2deg', 'radians', 'ravel', 'real',
137 'real_if_close', 'reciprocal', 'record', 'remainder', 'repeat', 'reshape',
138 'resize', 'right_shift', 'rint', 'roll', 'rollaxis', 'roots', 'rot90',
139 'round', 'searchsorted', 'select', 'setbufsize', 'setdiff1d', 'seterr',
140 'setxor1d', 'shape', 'short', 'sign', 'signbit', 'signedinteger', 'sin',
141 'sinc', 'single', 'sinh', 'size', 'sort', 'sort_complex', 'spacing',
142 'split', 'sqrt', 'square', 'squeeze', 'std', 'subtract', 'sum', 'swapaxes',
143 'take', 'tan', 'tanh', 'tensordot', 'tile', 'trace', 'transpose', 'tri',
144 'tril', 'tril_indices', 'tril_indices_from', 'trim_zeros', 'triu',
145 'triu_indices', 'triu_indices_from', 'true_divide', 'trunc', 'ubyte',
146 'uint', 'uint32', 'union1d', 'unique', 'unravel_index', 'unsignedinteger',
147 'unwrap', 'ushort', 'vander', 'var', 'vdot', 'vectorize', 'vsplit',
148 'vstack', 'where', 'zeros', 'zeros_like')
151FROM_NUMPY_FINANCIAL = ('fv', 'ipmt', 'irr', 'mirr', 'nper', 'npv',
152 'pmt', 'ppmt', 'pv', 'rate')
154NUMPY_RENAMES = {'ln': 'log', 'asin': 'arcsin', 'acos': 'arccos',
155 'atan': 'arctan', 'atan2': 'arctan2', 'atanh':
156 'arctanh', 'acosh': 'arccosh', 'asinh': 'arcsinh'}
158if HAS_NUMPY:
159 FROM_NUMPY = tuple(set(FROM_NUMPY))
160 FROM_NUMPY = tuple(sym for sym in FROM_NUMPY if hasattr(numpy, sym))
161 NUMPY_RENAMES = {sym: value for sym, value in NUMPY_RENAMES.items() if hasattr(numpy, value)}
163 NUMPY_TABLE = {}
164 for sym in FROM_NUMPY:
165 obj = getattr(numpy, sym, None)
166 if obj is not None:
167 NUMPY_TABLE[sym] = obj
169 for sname, sym in NUMPY_RENAMES.items():
170 obj = getattr(numpy, sym, None)
171 if obj is not None:
172 NUMPY_TABLE[sname] = obj
174 if HAS_NUMPY_FINANCIAL:
175 for sym in FROM_NUMPY_FINANCIAL:
176 obj = getattr(numpy_financial, sym, None)
177 if obj is not None:
178 NUMPY_TABLE[sym] = obj
180else:
181 NUMPY_TABLE = {}
184def _open(filename, mode='r', buffering=-1, encoding=None):
185 """read only version of open()"""
186 if mode not in ('r', 'rb', 'rU'):
187 raise RuntimeError("Invalid open file mode, must be 'r', 'rb', or 'rU'")
188 if buffering > MAX_OPEN_BUFFER:
189 raise RuntimeError(f"Invalid buffering value, max buffer size is {MAX_OPEN_BUFFER}")
190 return open(filename, mode, buffering, encoding=encoding)
193def _type(x):
194 """type that prevents varargs and varkws"""
195 return type(x).__name__
198LOCALFUNCS = {'open': _open, 'type': _type}
201# Safe versions of functions to prevent denial of service issues
203def safe_pow(base, exp):
204 """safe version of pow"""
205 if isinstance(exp, numbers.Number):
206 if exp > MAX_EXPONENT:
207 raise RuntimeError(f"Invalid exponent, max exponent is {MAX_EXPONENT}")
208 elif HAS_NUMPY and isinstance(exp, numpy.ndarray):
209 if numpy.nanmax(exp) > MAX_EXPONENT:
210 raise RuntimeError(f"Invalid exponent, max exponent is {MAX_EXPONENT}")
211 return base ** exp
214def safe_mult(arg1, arg2):
215 """safe version of multiply"""
216 if isinstance(arg1, str) and isinstance(arg2, int) and len(arg1) * arg2 > MAX_STR_LEN:
217 raise RuntimeError(f"String length exceeded, max string length is {MAX_STR_LEN}")
218 return arg1 * arg2
221def safe_add(arg1, arg2):
222 """safe version of add"""
223 if isinstance(arg1, str) and isinstance(arg2, str) and len(arg1) + len(arg2) > MAX_STR_LEN:
224 raise RuntimeError(f"String length exceeded, max string length is {MAX_STR_LEN}")
225 return arg1 + arg2
228def safe_lshift(arg1, arg2):
229 """safe version of lshift"""
230 if isinstance(arg2, numbers.Number):
231 if arg2 > MAX_SHIFT:
232 raise RuntimeError(f"Invalid left shift, max left shift is {MAX_SHIFT}")
233 elif HAS_NUMPY and isinstance(arg2, numpy.ndarray):
234 if numpy.nanmax(arg2) > MAX_SHIFT:
235 raise RuntimeError(f"Invalid left shift, max left shift is {MAX_SHIFT}")
236 return arg1 << arg2
239OPERATORS = {ast.Is: lambda a, b: a is b,
240 ast.IsNot: lambda a, b: a is not b,
241 ast.In: lambda a, b: a in b,
242 ast.NotIn: lambda a, b: a not in b,
243 ast.Add: safe_add,
244 ast.BitAnd: lambda a, b: a & b,
245 ast.BitOr: lambda a, b: a | b,
246 ast.BitXor: lambda a, b: a ^ b,
247 ast.Div: lambda a, b: a / b,
248 ast.FloorDiv: lambda a, b: a // b,
249 ast.LShift: safe_lshift,
250 ast.RShift: lambda a, b: a >> b,
251 ast.Mult: safe_mult,
252 ast.Pow: safe_pow,
253 ast.MatMult: lambda a, b: a @ b,
254 ast.Sub: lambda a, b: a - b,
255 ast.Mod: lambda a, b: a % b,
256 ast.And: lambda a, b: a and b,
257 ast.Or: lambda a, b: a or b,
258 ast.Eq: lambda a, b: a == b,
259 ast.Gt: lambda a, b: a > b,
260 ast.GtE: lambda a, b: a >= b,
261 ast.Lt: lambda a, b: a < b,
262 ast.LtE: lambda a, b: a <= b,
263 ast.NotEq: lambda a, b: a != b,
264 ast.Invert: lambda a: ~a,
265 ast.Not: lambda a: not a,
266 ast.UAdd: lambda a: +a,
267 ast.USub: lambda a: -a}
270def valid_symbol_name(name):
271 """Determine whether the input symbol name is a valid name.
273 Arguments
274 ---------
275 name : str
276 name to check for validity.
278 Returns
279 --------
280 valid : bool
281 whether name is a a valid symbol name
283 This checks for Python reserved words and that the name matches
284 the regular expression ``[a-zA-Z_][a-zA-Z0-9_]``
285 """
286 if name in RESERVED_WORDS:
287 return False
289 gen = generate_tokens(io.BytesIO(name.encode('utf-8')).readline)
290 typ, _, start, end, _ = next(gen)
291 if typ == tk_ENCODING:
292 typ, _, start, end, _ = next(gen)
293 return typ == tk_NAME and start == (1, 0) and end == (1, len(name))
296def op2func(oper):
297 """Return function for operator nodes."""
298 return OPERATORS[oper.__class__]
301class Empty:
302 """Empty class."""
303 def __init__(self):
304 """TODO: docstring in public method."""
305 return
307 def __nonzero__(self):
308 """Empty is TODO: docstring in magic method."""
309 return False
311 def __repr__(self):
312 """Empty is TODO: docstring in magic method."""
313 return "Empty"
315ReturnedNone = Empty()
317class ExceptionHolder:
318 """Basic exception handler."""
319 def __init__(self, node, exc=None, msg='', expr=None, lineno=None):
320 """TODO: docstring in public method."""
321 self.node = node
322 self.expr = expr
323 self.msg = msg
324 self.exc = exc
325 self.lineno = lineno
326 self.exc_info = exc_info()
327 if self.exc is None and self.exc_info[0] is not None:
328 self.exc = self.exc_info[0]
329 if self.msg == '' and self.exc_info[1] is not None:
330 self.msg = str(self.exc_info[1])
332 def get_error(self):
333 """Retrieve error data."""
334 col_offset = -1
335 if self.node is not None:
336 try:
337 col_offset = self.node.col_offset
338 except AttributeError:
339 pass
340 try:
341 exc_name = self.exc.__name__
342 except AttributeError:
343 exc_name = str(self.exc)
344 if exc_name in (None, 'None'):
345 exc_name = 'UnknownError'
347 out = [f" {self.expr}"]
348 if col_offset > 0:
349 out.append(f" {col_offset*' '}^^^^")
350 out.append(f"{exc_name}: {self.msg}")
351 return (exc_name, '\n'.join(out))
353 def __repr__(self):
354 return f"ExceptionHolder({self.exc}, {self.msg})"
356class NameFinder(ast.NodeVisitor):
357 """Find all symbol names used by a parsed node."""
359 def __init__(self):
360 """TODO: docstring in public method."""
361 self.names = []
362 ast.NodeVisitor.__init__(self)
364 def generic_visit(self, node):
365 """TODO: docstring in public method."""
366 if node.__class__.__name__ == 'Name':
367 if node.id not in self.names:
368 self.names.append(node.id)
369 ast.NodeVisitor.generic_visit(self, node)
372def get_ast_names(astnode):
373 """Return symbol Names from an AST node."""
374 finder = NameFinder()
375 finder.generic_visit(astnode)
376 return finder.names
379def valid_varname(name):
380 "is this a valid variable name"
381 return name.isidentifier() and name not in RESERVED_WORDS
384class Group(dict):
385 """
386 Group: a container of objects that can be accessed either as an object attributes
387 or dictionary key/value. Attribute names must follow Python naming conventions.
388 """
389 def __init__(self, name=None, searchgroups=None, **kws):
390 if name is None:
391 name = hex(id(self))
392 self.__name__ = name
393 dict.__init__(self, **kws)
394 self._searchgroups = searchgroups
396 def __setattr__(self, name, value):
397 if not valid_varname(name):
398 raise SyntaxError(f"invalid attribute name '{name}'")
399 self[name] = value
401 def __getattr__(self, name, default=None):
402 if name in self:
403 return self[name]
404 if default is not None:
405 return default
406 raise KeyError(f"no attribute named '{name}'")
408 def __setitem__(self, name, value):
409 if valid_varname(name):
410 dict.__setitem__(self, name, value)
411 else: # raise SyntaxError(f"invalid attribute name '{name}'")
412 return setattr(self, name, value)
414 def get(self, key, default=None):
415 val = self.__getattr__(key, ReturnedNone)
416 if not isinstance(val, Empty):
417 return val
418 searchgroups = self._searchgroups
419 if searchgroups is not None:
420 for sgroup in searchgroups:
421 grp = self.__getattr__(sgroup, None)
422 if isinstance(grp, (Group, dict)):
423 val = grp.__getattr__(key, ReturnedNone)
424 if not isinstance(val, Empty):
425 return val
426 return default
429 def __repr__(self):
430 keys = [a for a in self.keys() if a != '__name__']
431 return f"Group('{self.__name__}', {len(keys)} symbols)"
433 def _repr_html_(self):
434 """HTML representation for Jupyter notebook"""
435 html = [f"<table><caption>Group('{self.__name__}')</caption>",
436 "<tr><th>Attribute</th><th>DataType</th><th><b>Value</b></th></tr>"]
437 for key, val in self.items():
438 html.append(f"""
439<tr><td>{key}</td><td><i>{type(val).__name__}</i></td>
440 <td>{repr(val):.75s}</td>
441</tr>""")
442 html.append("</table>")
443 return '\n'.join(html)
446def make_symbol_table(use_numpy=True, nested=False, top=True, **kws):
447 """Create a default symboltable, taking dict of user-defined symbols.
449 Arguments
450 ---------
451 numpy : bool, optional
452 whether to include symbols from numpy [True]
453 nested : bool, optional
454 whether to make a "new-style" nested table instead of a plain dict [False]
455 top : bool, optional
456 whether this is the top-level table in a nested-table [True]
457 kws : optional
458 additional symbol name, value pairs to include in symbol table
460 Returns
461 --------
462 symbol_table : dict or nested Group
463 a symbol table that can be used in `asteval.Interpereter`
465 """
466 if nested:
467 name = '_'
468 if top:
469 name = '_main'
470 if 'name' in kws:
471 name = kws.pop('name')
472 symtable = Group(name=name, Group=Group)
473 else:
474 symtable = {}
476 symtable.update(BUILTINS_TABLE)
477 symtable.update(LOCALFUNCS)
478 symtable.update(kws)
479 math_functions = dict(MATH_TABLE.items())
480 if use_numpy:
481 math_functions.update(NUMPY_TABLE)
483 if nested:
484 symtable['math'] = Group(name='math', **math_functions)
485 symtable['Group'] = Group
486 symtable._searchgroups = ('math',)
487 else:
488 symtable.update(math_functions)
489 symtable.update(**kws)
490 return symtable
493class Procedure:
494 """Procedure: user-defined function for asteval.
496 This stores the parsed ast nodes as from the 'functiondef' ast node
497 for later evaluation.
499 """
501 def __init__(self, name, interp, doc=None, lineno=0,
502 body=None, args=None, kwargs=None,
503 vararg=None, varkws=None):
504 """TODO: docstring in public method."""
505 self.__ininit__ = True
506 self.name = name
507 self.__name__ = self.name
508 self.__asteval__ = interp
509 self.raise_exc = self.__asteval__.raise_exception
510 self.__doc__ = doc
511 self.body = body
512 self.argnames = args
513 self.kwargs = kwargs
514 self.vararg = vararg
515 self.varkws = varkws
516 self.lineno = lineno
517 self.__ininit__ = False
519 def __setattr__(self, attr, val):
520 if not getattr(self, '__ininit__', True):
521 self.raise_exc(None, exc=TypeError,
522 msg="procedure is read-only")
523 self.__dict__[attr] = val
525 def __dir__(self):
526 return ['_getdoc', 'argnames', 'kwargs', 'name', 'vararg', 'varkws']
528 def _getdoc(self):
529 doc = self.__doc__
530 if isinstance(doc, ast.Constant):
531 doc = doc.value
532 return doc
534 def __repr__(self):
535 """TODO: docstring in magic method."""
536 sig = self._signature()
537 rep = f"<Procedure {sig}>"
538 doc = self._getdoc()
539 if doc is not None:
540 rep = f"{rep}\n {doc}"
541 return rep
543 def _signature(self):
544 "call signature"
545 sig = ""
546 if len(self.argnames) > 0:
547 sig = sig + ', '.join(self.argnames)
548 if self.vararg is not None:
549 sig = sig + f"*{self.vararg}"
550 if len(self.kwargs) > 0:
551 if len(sig) > 0:
552 sig = f"{sig}, "
553 _kw = [f"{k}={v}" for k, v in self.kwargs]
554 sig = f"{sig}{', '.join(_kw)}"
556 if self.varkws is not None:
557 sig = f"{sig}, **{self.varkws}"
558 return f"{self.name}({sig})"
560 def __call__(self, *args, **kwargs):
561 """TODO: docstring in public method."""
562 topsym = self.__asteval__.symtable
563 if self.__asteval__.config.get('nested_symtable', False):
564 sargs = {'_main': topsym}
565 sgroups = topsym.get('_searchgroups', None)
566 if sgroups is not None:
567 for sxname in sgroups:
568 sargs[sxname] = topsym.get(sxname)
571 symlocals = Group(name=f'symtable_{self.name}_', **sargs)
572 symlocals._searchgroups = list(sargs.keys())
573 else:
574 symlocals = {}
576 args = list(args)
577 nargs = len(args)
578 nkws = len(kwargs)
579 nargs_expected = len(self.argnames)
581 # check for too few arguments, but the correct keyword given
582 if (nargs < nargs_expected) and nkws > 0:
583 for name in self.argnames[nargs:]:
584 if name in kwargs:
585 args.append(kwargs.pop(name))
586 nargs = len(args)
587 nargs_expected = len(self.argnames)
588 nkws = len(kwargs)
589 if nargs < nargs_expected:
590 msg = f"{self.name}() takes at least"
591 msg = f"{msg} {nargs_expected} arguments, got {nargs}"
592 self.raise_exc(None, exc=TypeError, msg=msg)
593 # check for multiple values for named argument
594 if len(self.argnames) > 0 and kwargs is not None:
595 msg = "multiple values for keyword argument"
596 for targ in self.argnames:
597 if targ in kwargs:
598 msg = f"{msg} '{targ}' in Procedure {self.name}"
599 self.raise_exc(None, exc=TypeError, msg=msg, lineno=self.lineno)
601 # check more args given than expected, varargs not given
602 if nargs != nargs_expected:
603 msg = None
604 if nargs < nargs_expected:
605 msg = f"not enough arguments for Procedure {self.name}()"
606 msg = f"{msg} (expected {nargs_expected}, got {nargs}"
607 self.raise_exc(None, exc=TypeError, msg=msg)
609 if nargs > nargs_expected and self.vararg is None:
610 if nargs - nargs_expected > len(self.kwargs):
611 msg = f"too many arguments for {self.name}() expected at most"
612 msg = f"{msg} {len(self.kwargs)+nargs_expected}, got {nargs}"
613 self.raise_exc(None, exc=TypeError, msg=msg)
615 for i, xarg in enumerate(args[nargs_expected:]):
616 kw_name = self.kwargs[i][0]
617 if kw_name not in kwargs:
618 kwargs[kw_name] = xarg
620 for argname in self.argnames:
621 symlocals[argname] = args.pop(0)
623 try:
624 if self.vararg is not None:
625 symlocals[self.vararg] = tuple(args)
627 for key, val in self.kwargs:
628 if key in kwargs:
629 val = kwargs.pop(key)
630 symlocals[key] = val
632 if self.varkws is not None:
633 symlocals[self.varkws] = kwargs
635 elif len(kwargs) > 0:
636 msg = f"extra keyword arguments for Procedure {self.name}: "
637 msg = msg + ','.join(list(kwargs.keys()))
638 self.raise_exc(None, msg=msg, exc=TypeError,
639 lineno=self.lineno)
641 except (ValueError, LookupError, TypeError,
642 NameError, AttributeError):
643 msg = f"incorrect arguments for Procedure {self.name}"
644 self.raise_exc(None, msg=msg, lineno=self.lineno)
646 if self.__asteval__.config.get('nested_symtable', False):
647 save_symtable = self.__asteval__.symtable
648 self.__asteval__.symtable = symlocals
649 else:
650 save_symtable = self.__asteval__.symtable.copy()
651 self.__asteval__.symtable.update(symlocals)
653 self.__asteval__.retval = None
654 self.__asteval__._calldepth += 1
655 retval = None
657 # evaluate script of function
658 for node in self.body:
659 self.__asteval__.run(node, expr='<>', lineno=self.lineno)
660 if len(self.__asteval__.error) > 0:
661 break
662 if self.__asteval__.retval is not None:
663 retval = self.__asteval__.retval
664 self.__asteval__.retval = None
665 if retval is ReturnedNone:
666 retval = None
667 break
669 self.__asteval__.symtable = save_symtable
670 self.__asteval__._calldepth -= 1
671 symlocals = None
672 return retval