• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

nbiotcloud / ucdp / 19643393352

24 Nov 2025 05:30PM UTC coverage: 90.304% (-0.009%) from 90.313%
19643393352

push

github

iccode17
implement sv consts without width

6 of 7 new or added lines in 2 files covered. (85.71%)

4759 of 5270 relevant lines covered (90.3%)

13.5 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.16
/src/ucdp/exprparser.py
1
#
2
# MIT License
3
#
4
# Copyright (c) 2024-2025 nbiotcloud
5
#
6
# Permission is hereby granted, free of charge, to any person obtaining a copy
7
# of this software and associated documentation files (the "Software"), to deal
8
# in the Software without restriction, including without limitation the rights
9
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
# copies of the Software, and to permit persons to whom the Software is
11
# furnished to do so, subject to the following conditions:
12
#
13
# The above copyright notice and this permission notice shall be included in all
14
# copies or substantial portions of the Software.
15
#
16
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
# SOFTWARE.
23
#
24
"""
25
Expression Parser.
26
"""
27

28
from collections.abc import Iterable
15✔
29
from functools import cached_property
15✔
30
from typing import Any
15✔
31

32
from ._castingnamespace import CastingNamespace
15✔
33
from .consts import RE_IDENTIFIER
15✔
34
from .exceptions import InvalidExpr
15✔
35
from .expr import (
15✔
36
    _RE_CONST,
37
    BoolOp,
38
    ConcatExpr,
39
    ConstExpr,
40
    Expr,
41
    Log2Expr,
42
    MaximumExpr,
43
    MinimumExpr,
44
    Op,
45
    SliceOp,
46
    SOp,
47
    TernaryExpr,
48
    _parse_const,
49
)
50
from .namespace import Namespace
15✔
51
from .note import Note
15✔
52
from .object import Object, computed_field
15✔
53
from .typebase import BaseScalarType, BaseType
15✔
54
from .typescalar import BoolType
15✔
55

56
Parseable = Expr | str | int | BaseType | list | tuple
15✔
57
Constable = int | str | ConstExpr
15✔
58
Concatable = list | tuple | ConcatExpr
15✔
59
Only = type[Expr] | Iterable[type[Expr]] | type[Note]
15✔
60
Types = type[BaseType] | Iterable[type[BaseType]]
15✔
61

62

63
class _Globals(dict):
15✔
64
    def __init__(self, globals: dict, namespace: Namespace | CastingNamespace | None, context: str | None):
15✔
65
        super().__init__(globals)
15✔
66
        self.namespace = namespace
15✔
67
        self.context = context
15✔
68

69
    def __missing__(self, key):
15✔
70
        if self.namespace:
15✔
71
            try:
15✔
72
                return self.namespace.get_dym(key)
15✔
73
            except ValueError as err:
15✔
74
                raise NameError(f"{self.context}: {err}") from None
15✔
75
        return NameError(key)
15✔
76

77

78
class ExprParser(Object):
15✔
79
    """
80
    ExprParser.
81

82
    Attributes:
83
        namespace (Namespace): Symbol namespace
84
    """
85

86
    namespace: Namespace | CastingNamespace | None = None
15✔
87
    context: str | None = None
15✔
88

89
    @computed_field
15✔
90
    @cached_property
15✔
91
    def _globals(self) -> _Globals:
15✔
92
        globals_ = {
15✔
93
            # Expressions
94
            "Op": Op,
95
            "SOp": SOp,
96
            "BoolOp": BoolOp,
97
            "SliceOp": SliceOp,
98
            "ConstExpr": ConstExpr,
99
            "ConcatExpr": ConcatExpr,
100
            "TernaryExpr": TernaryExpr,
101
            "Log2Expr": Log2Expr,
102
            "MinimumExpr": MinimumExpr,
103
            "MaximumExpr": MaximumExpr,
104
            # Helper
105
            "const": self.const,
106
            "concat": self.concat,
107
            "ternary": self.ternary,
108
            "log2": self.log2,
109
            "minimum": self.minimum,
110
            "maximum": self.maximum,
111
        }
112
        return _Globals(globals=globals_, namespace=self.namespace, context=self.context)
15✔
113

114
    def __call__(self, expr: Parseable, only: Only | None = None, types: Types | None = None) -> Expr:
15✔
115
        """
116
        Parse Expression.
117

118
        This is an alias to `parse`.
119

120
        Args:
121
            expr: Expression
122

123
        Keyword Args:
124
            only: Limit expression to these final element type.
125
            types: Limit expression type to to these types.
126
        """
127
        return self.parse(expr, only=only, types=types)
15✔
128

129
    def parse_note(self, expr: Parseable | Note, only: Only | None = None, types: Types | None = None) -> Expr | Note:
15✔
130
        """
131
        Parse Expression or Note.
132

133
        Args:
134
            expr: Expression
135

136
        Keyword Args:
137
            only: Limit expression to these final element type.
138
            types: Limit expression type to to these types.
139
        """
140
        if isinstance(expr, Note):
15✔
141
            self._check(expr, only=only, types=types)
15✔
142
            return expr
15✔
143
        return self.parse(expr, only=only, types=types)
15✔
144

145
    def parse(self, expr: Parseable, only: Only | None = None, types: Types | None = None) -> Expr:
15✔
146
        """
147
        Parse Expression.
148

149
        Args:
150
            expr: Expression
151

152
        Keyword Args:
153
            only: Limit expression to these final element type.
154
            types: Limit expression type to to these types.
155

156
        ??? Example "Expression Parser Examples"
157
            Basics:
158

159
                >>> import ucdp as u
160
                >>> p = u.ExprParser()
161
                >>> p.parse(10)
162
                ConstExpr(IntegerType(default=10))
163
                >>> p.parse('3h3')
164
                ConstExpr(UintType(3, default=3))
165
                >>> p.parse('3h3') * p.const(2)
166
                Op(ConstExpr(UintType(3, default=3)), '*', ConstExpr(IntegerType(default=2)))
167
                >>> p.parse((10, '10'))
168
                ConcatExpr((ConstExpr(IntegerType(default=10)), ConstExpr(IntegerType(default=10))))
169
                >>> p = u.ExprParser(namespace=u.Idents([
170
                ...     u.Signal(u.UintType(16, default=15), 'uint_s'),
171
                ...     u.Signal(u.SintType(16, default=-15), 'sint_s'),
172
                ... ]))
173
                >>> expr = p.parse('uint_s[2]')
174
                >>> expr
175
                SliceOp(Signal(UintType(16, default=15), 'uint_s'), Slice('2'))
176
                >>> expr = p.parse('uint_s * sint_s[2:1]')
177
                >>> expr
178
                Op(Signal(UintType(16, ...), 'uint_s'), '*', SliceOp(Signal(SintType(16, ...), 'sint_s'), Slice('2:1')))
179
                >>> int(expr)
180
                0
181

182
            A more complex:
183

184
                >>> namespace = u.Idents([
185
                ...     u.Signal(u.UintType(2), 'a_s'),
186
                ...     u.Signal(u.UintType(4), 'b_s'),
187
                ...     u.Signal(u.SintType(8), 'c_s'),
188
                ...     u.Signal(u.SintType(16), 'd_s'),
189
                ... ])
190
                >>> p = u.ExprParser(namespace=namespace)
191
                >>> expr = p.parse("ternary(b_s == const('4h3'), a_s, c_s)")
192
                >>> expr
193
                TernaryExpr(BoolOp(Signal(UintType(4), 'b_s'), '==', ..., Signal(SintType(8), 'c_s'))
194

195
                Syntax Errors:
196

197
                >>> parse("sig_s[2")  # doctest: +SKIP
198
                Traceback (most recent call last):
199
                ...
200
                u.exceptions.InvalidExpr: 'sig_s[2': '[' was never closed (<string>, line 1)
201
        """
202
        result: Expr
203
        if isinstance(expr, Expr):
15✔
204
            result = expr
15✔
205
        elif isinstance(expr, BaseType):
15✔
206
            result = ConstExpr(expr)
15✔
207
        else:
208
            try:
15✔
209
                if isinstance(expr, (list, tuple)):
15✔
210
                    result = self.concat(expr)
15✔
211
                else:
212
                    try:
15✔
213
                        result = self.const(expr)
15✔
214
                    except InvalidExpr:
15✔
215
                        result = self._parse(str(expr))
15✔
216
            except NameError as exc:
15✔
217
                raise exc
15✔
218
        self._check(result, only=only, types=types)
15✔
219
        return result
15✔
220

221
    def _check(self, expr: Expr | Note, only: Only | None, types: Types | None) -> None:
15✔
222
        if only and not isinstance(expr, only):  # type: ignore[arg-type]
15✔
223
            raise ValueError(f"{expr!r} is not a {only}. It is a {type(expr)}") from None
15✔
224
        if types:
15✔
225
            if isinstance(expr, Note):
15✔
226
                raise ValueError(f"{expr!r} does not meet type_ {types}.") from None
15✔
227
            if not isinstance(expr.type_, types):  # type: ignore[arg-type]
15✔
228
                raise ValueError(f"{expr!r} requires type_ {types}. It is a {expr.type_}") from None
15✔
229

230
    def _parse(self, expr: str) -> Expr:
15✔
231
        if self.namespace:
15✔
232
            # avoid eval call on simple identifiers
233
            if isinstance(expr, str) and RE_IDENTIFIER.match(expr):
15✔
234
                try:
15✔
235
                    return self.namespace[expr]
15✔
236
                except ValueError:
15✔
237
                    pass
15✔
238
        if _RE_CONST.fullmatch(expr.strip()):
15✔
NEW
239
            return _parse_const(expr)
×
240
        try:
15✔
241
            globals: dict[str, Any] = self._globals  # type: ignore[assignment]
15✔
242
            return eval(expr, globals)  # noqa: S307
15✔
243
        except TypeError:
15✔
244
            raise InvalidExpr(expr) from None
15✔
245
        except SyntaxError as exc:
15✔
246
            raise InvalidExpr(f"{expr!r}: {exc!s}") from None
15✔
247

248
    def const(self, value: Constable) -> ConstExpr:
15✔
249
        """
250
        Parse Constant.
251

252
        ??? Example "Parser Example"
253
            Basics:
254

255
                >>> import ucdp as u
256
                >>> p = u.ExprParser()
257
                >>> p.const('10')
258
                ConstExpr(IntegerType(default=10))
259
                >>> p.const(10)
260
                ConstExpr(IntegerType(default=10))
261
                >>> p.const("10'd20")
262
                ConstExpr(UintType(10, default=20))
263
                >>> p.const(u.ConstExpr(u.UintType(10, default=20)))
264
                ConstExpr(UintType(10, default=20))
265
                >>> p.const("4'h4")
266
                ConstExpr(UintType(4, default=4))
267
                >>> p.const("4'sh4")
268
                ConstExpr(SintType(4, default=4))
269
                >>> p.const("4'shC")
270
                ConstExpr(SintType(4, default=-4))
271
        """
272
        if isinstance(value, ConstExpr):
15✔
273
            return value
15✔
274
        return _parse_const(value)
15✔
275

276
    def concat(self, value: Concatable) -> ConcatExpr:
15✔
277
        """
278
        Parse ConcatExpr.
279

280
        ??? Example "Concat Parser Example"
281
            Basics:
282

283
                >>> import ucdp as u
284
                >>> p = u.ExprParser()
285
                >>> p.concat((10, "20"))
286
                ConcatExpr((ConstExpr(IntegerType(default=10)), ConstExpr(IntegerType(default=20))))
287

288
                >>> bool(p.concat((10, "20")))
289
                True
290
        """
291
        if isinstance(value, ConcatExpr):
15✔
292
            return value
15✔
293
        return ConcatExpr(tuple(self.parse(item) for item in value))
15✔
294

295
    def ternary(self, cond: Parseable, one: Parseable, other: Parseable) -> TernaryExpr:
15✔
296
        """
297
        TernaryExpr Statement.
298

299
        ??? Example "Ternary Parser Example"
300
            Basics:
301

302
                >>> import ucdp as u
303
                >>> cond = u.Signal(u.UintType(2), 'if_s') == u.ConstExpr(u.UintType(2, default=1))
304
                >>> one = u.Signal(u.UintType(16, default=10), 'one_s')
305
                >>> other = u.Signal(u.UintType(16, default=20), 'other_s')
306
                >>> p = u.ExprParser()
307
                >>> expr = p.ternary(cond, one, other)
308
                >>> expr
309
                TernaryExpr(BoolOp(Signal(UintType(2), 'if_s'), '==', ..., Signal(UintType(16, default=20), 'other_s'))
310
                >>> int(expr)
311
                20
312
                >>> expr.type_
313
                UintType(16, default=10)
314
        """
315
        condp: BoolOp = self.parse(cond, only=BoolOp)  # type:ignore[assignment]
15✔
316
        onep = self.parse(one)
15✔
317
        otherp = self.parse(other)
15✔
318
        return TernaryExpr(cond=condp, one=onep, other=otherp)
15✔
319

320
    def log2(self, expr: Parseable):
15✔
321
        """
322
        Ceiling Logarithm to base of 2.
323

324
        ??? Example "Log2 Parser Example"
325
            Basics:
326

327
                >>> import ucdp as u
328
                >>> p = u.ExprParser()
329
                >>> log = p.log2("8'h8")
330
                >>> log
331
                Log2Expr(ConstExpr(UintType(8, default=8)))
332
                >>> int(log)
333
                3
334
                >>> p.parse("log2('8h8')")
335
                Log2Expr(ConstExpr(UintType(8, default=8)))
336
        """
337
        return Log2Expr(self.parse(expr))
15✔
338

339
    def minimum(self, *items):
15✔
340
        """
341
        Lower value of `one` and `other`.
342

343
        ??? Example "Minimum Parser Example"
344
            Basics:
345

346
                >>> import ucdp as u
347
                >>> p = u.ExprParser()
348
                >>> val = p.minimum("8'h8", "8'h3")
349
                >>> val
350
                MinimumExpr((ConstExpr(UintType(8, default=8)), ConstExpr(UintType(8, default=3))))
351
                >>> int(val)
352
                3
353
                >>> p.parse("minimum('8h8', '8h3')")
354
                MinimumExpr((ConstExpr(UintType(8, default=8)), ConstExpr(UintType(8, default=3))))
355
        """
356
        parsed = tuple(self.parse(item) for item in items)
15✔
357
        return MinimumExpr(parsed)
15✔
358

359
    def maximum(self, *items):
15✔
360
        """
361
        Higher value of `one` and `other`.
362

363
        ??? Example "Maximum Parser Example"
364
            Basics:
365

366
                >>> import ucdp as u
367
                >>> p = u.ExprParser()
368
                >>> val = p.maximum("8'h8", "8'h3")
369
                >>> val
370
                MaximumExpr((ConstExpr(UintType(8, default=8)), ConstExpr(UintType(8, default=3))))
371
                >>> int(val)
372
                8
373
                >>> p.parse("maximum('8h8', '8h3')")
374
                MaximumExpr((ConstExpr(UintType(8, default=8)), ConstExpr(UintType(8, default=3))))
375
        """
376
        parsed = tuple(self.parse(item) for item in items)
15✔
377
        return MaximumExpr(parsed)
15✔
378

379

380
def cast_booltype(expr):
15✔
381
    """Cast to Boolean."""
382
    type_ = expr.type_
15✔
383
    if isinstance(type_, BoolType):
15✔
384
        return expr
15✔
385
    if isinstance(type_, BaseScalarType) and int(type_.width) == 1:
15✔
386
        return expr == 1
15✔
387
    raise ValueError(f"{expr} does not result in bool")
15✔
388

389

390
_PARSER = ExprParser()
15✔
391
const = _PARSER.const
15✔
392
concat = _PARSER.concat
15✔
393
ternary = _PARSER.ternary
15✔
394
log2 = _PARSER.log2
15✔
395
minimum = _PARSER.minimum
15✔
396
maximum = _PARSER.minimum
15✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc