File: parse_synaptic_generator_syntax.py

package info (click to toggle)
brian 2.9.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 6,872 kB
  • sloc: python: 51,820; cpp: 2,033; makefile: 108; sh: 72
file content (187 lines) | stat: -rw-r--r-- 5,771 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
import ast

from brian2.parsing.rendering import NodeRenderer

__all__ = ["parse_synapse_generator"]


def _cname(obj):
    return obj.__class__.__name__


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}


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,
}


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