from brian2 import *
from brian2.parsing.rendering import NodeRenderer
import ast
__all__ = ['parse_synapse_generator']
def _cname(obj):
return obj.__class__.__name__
[docs]def handle_range(*args, **kwds):
"""
Checks the arguments/keywords for the range iterator
Should have 1-3 positional arguments.
Returns a dict with keys low, high, step. Default values are
low=0, step=1.
"""
if len(args) == 0 or len(args) > 3:
raise SyntaxError("Range iterator takes 1-3 positional arguments.")
if len(kwds):
raise SyntaxError("Range iterator doesn't accept any keyword "
"arguments.")
if len(args) == 1:
high = args[0]
low = '0'
step = '1'
elif len(args) == 2:
low, high = args
step = '1'
else:
low, high, step = args
return {'low': low, 'high': high, 'step': step}
[docs]def handle_sample(*args, **kwds):
"""
Checks the arguments/keywords for the sample iterator
Should have 1-3 positional arguments and 1 keyword argument (either p or
size).
Returns a dict with keys ``low, high, step, sample_size, p, size``. Default
values are ``low=0``, ``step=1`. Sample size will be either ``'random'`` or
``'fixed'``. In the first case, ``p`` will have a value and size will be
``None`` (and vice versa for the second case).
"""
if len(args) == 0 or len(args) > 3:
raise SyntaxError("Sample iterator takes 1-3 positional arguments.")
if len(kwds) != 1 or ('p' not in kwds and 'size' not in kwds):
raise SyntaxError("Sample iterator accepts one keyword argument, "
"either 'p' or 'size'.")
if len(args) == 1:
high = args[0]
low = '0'
step = '1'
elif len(args) == 2:
low, high = args
step = '1'
else:
low, high, step = args
if 'p' in kwds:
sample_size = 'random'
p = kwds['p']
size = None
else:
sample_size = 'fixed'
size = kwds['size']
p = None
return {'low': low, 'high': high, 'step': step,
'p': p, 'size': size, 'sample_size': sample_size}
iterator_function_handlers = {
'range': handle_range,
'sample': handle_sample,
}
[docs]def parse_synapse_generator(expr):
"""
Returns a parsed form of a synapse generator expression.
The general form is:
``element for inner_variable in iterator_func(...)``
or
``element for inner_variable in iterator_func(...) if if_expression``
Returns a dictionary with keys:
``original_expression``
The original expression as a string.
``element``
As above, a string expression.
``inner_variable``
A variable name, as above.
``iterator_func``
String. Either ``range`` or ``sample``.
``if_expression``
String expression or ``None``.
``iterator_kwds``
Dictionary of key/value pairs representing the keywords. See
`handle_range` and `handle_sample`.
"""
nr = NodeRenderer()
parse_error = ("Error parsing expression '%s'. Expression must have "
"generator syntax, for example 'k for k in range(i-10, "
"i+10)'." % expr)
try:
node = ast.parse(f'[{expr}]', mode='eval').body
except Exception as e:
raise SyntaxError(f"{parse_error} Error encountered was {e}")
if _cname(node) != 'ListComp':
raise SyntaxError(f"{parse_error} Expression is not a generator expression.")
element = node.elt
if len(node.generators) != 1:
raise SyntaxError(f"{parse_error} Generator expression must involve only one iterator.")
generator = node.generators[0]
target = generator.target
if _cname(target) != 'Name':
raise SyntaxError(f"{parse_error} Generator must iterate over a single variable (not tuple, etc.).")
inner_variable = target.id
iterator = generator.iter
if _cname(iterator) != 'Call' or _cname(iterator.func) != 'Name':
raise SyntaxError(parse_error + " Iterator expression must be one of "
"the supported functions: " +
str(list(iterator_function_handlers)))
iterator_funcname = iterator.func.id
if iterator_funcname not in iterator_function_handlers:
raise SyntaxError(parse_error + " Iterator expression must be one of "
"the supported functions: " +
str(list(iterator_function_handlers)))
if (getattr(iterator, 'starargs', None) is not None or
getattr(iterator, 'kwargs', None) is not None):
raise SyntaxError(f"{parse_error} Star arguments not supported.")
args = []
for argnode in iterator.args:
args.append(nr.render_node(argnode))
keywords = {}
for kwdnode in iterator.keywords:
keywords[kwdnode.arg] = nr.render_node(kwdnode.value)
try:
iterator_handler = iterator_function_handlers[iterator_funcname]
iterator_kwds = iterator_handler(*args, **keywords)
except SyntaxError as exc:
raise SyntaxError(f"{parse_error} {exc.msg}")
if len(generator.ifs) == 0:
condition = ast.parse('True', mode='eval').body
elif len(generator.ifs) > 1:
raise SyntaxError(f"{parse_error} Generator must have at most one if statement.")
else:
condition = generator.ifs[0]
parsed = {
'original_expression': expr,
'element': nr.render_node(element),
'inner_variable': inner_variable,
'iterator_func': iterator_funcname,
'iterator_kwds': iterator_kwds,
'if_expression': nr.render_node(condition),
}
return parsed