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

nbiotcloud / ucdp / 20165228389

12 Dec 2025 11:22AM UTC coverage: 90.397%. First build
20165228389

push

github

iccode17
complete define support

64 of 67 new or added lines in 8 files covered. (95.52%)

4867 of 5384 relevant lines covered (90.4%)

13.51 hits per line

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

98.4
/src/ucdp/exprresolver.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 Resolver.
26
"""
27

28
from typing import Any, ClassVar
15✔
29

30
from .define import Define
15✔
31
from .expr import (
15✔
32
    BoolOp,
33
    ConcatExpr,
34
    ConstExpr,
35
    Expr,
36
    Log2Expr,
37
    MaximumExpr,
38
    MinimumExpr,
39
    Op,
40
    RangeExpr,
41
    SliceOp,
42
    SOp,
43
    TernaryExpr,
44
)
45
from .ident import Ident, Idents
15✔
46
from .namespace import Namespace
15✔
47
from .note import Note
15✔
48
from .object import Object
15✔
49
from .slices import Slice
15✔
50
from .typearray import ArrayType
15✔
51
from .typebase import BaseScalarType, BaseType
15✔
52
from .typeenum import BaseEnumType
15✔
53
from .typefloat import DoubleType, FloatType
15✔
54
from .typescalar import BitType, BoolType, IntegerType, RailType, SintType, UintType
15✔
55
from .typestring import StringType
15✔
56

57

58
class ExprResolver(Object):
15✔
59
    """
60
    Expression Resolver.
61

62
    ??? Example "Maximum Parser Example"
63
        Basics:
64

65
            >>> import ucdp as u
66
            >>> idents = u.Idents([
67
            ...     u.Signal(u.UintType(16), 'uint_s'),
68
            ...     u.Signal(u.SintType(16), 'sint_s'),
69
            ... ])
70
            >>> parser = u.ExprParser(namespace=idents)
71
            >>> expr = parser.parse('uint_s') * parser.const(2)
72
            >>> expr
73
            Op(Signal(UintType(16), 'uint_s'), '*', ConstExpr(IntegerType(default=2)))
74

75
            >>> resolver = u.ExprResolver(namespace=idents)
76
            >>> resolver.resolve(expr)
77
            'uint_s * 2'
78
            >>> resolver.resolve(expr, brackets=True)
79
            '(uint_s * 2)'
80
    """
81

82
    namespace: Namespace | None = None
15✔
83
    remap: Idents | None = None
15✔
84

85
    _opremap: ClassVar[dict[str, str]] = {}
15✔
86
    _BRACKETTYPES: tuple[Any, ...] = (Op, BoolOp, SOp, TernaryExpr)
15✔
87

88
    def __call__(self, expr: Expr, brackets: bool = False) -> str:
15✔
89
        """
90
        Resolve.
91

92
        Args:
93
            expr: Expression
94
            brackets: Use brackets if necessary for topmost expr.
95
        """
96
        return self._resolve(expr, brackets=brackets)
15✔
97

98
    def resolve(self, expr: Expr | Note, brackets: bool = False) -> str:
15✔
99
        """
100
        Resolve.
101

102
        Args:
103
            expr: Expression
104
            brackets: Use brackets if necessary for topmost expr.
105
        """
106
        return self._resolve(expr, brackets=brackets)
15✔
107

108
    def _resolve(self, expr: Expr | Note, brackets: bool = False) -> str:  # noqa: C901, PLR0912
15✔
109
        if isinstance(expr, Ident):
15✔
110
            resolved = self._resolve_ident(expr)
15✔
111
        elif isinstance(expr, BoolOp):
15✔
112
            resolved = self._resolve_boolop(expr)
15✔
113
        elif isinstance(expr, SOp):
15✔
114
            resolved = self._resolve_sop(expr)
15✔
115
        elif isinstance(expr, Op):
15✔
116
            resolved = self._resolve_op(expr)
15✔
117
        elif isinstance(expr, SliceOp):
15✔
118
            resolved = self._resolve_sliceop(expr)
15✔
119
        elif isinstance(expr, ConstExpr):
15✔
120
            resolved = self._resolve_constexpr(expr)
15✔
121
        elif isinstance(expr, ConcatExpr):
15✔
122
            resolved = self._resolve_concatexpr(expr)
15✔
123
        elif isinstance(expr, TernaryExpr):
15✔
124
            resolved = self._resolve_ternaryexpr(expr)
15✔
125
        elif isinstance(expr, Log2Expr):
15✔
126
            resolved = self._resolve_log2expr(expr)
15✔
127
        elif isinstance(expr, MinimumExpr):
15✔
128
            resolved = self._resolve_minimumexpr(expr)
15✔
129
        elif isinstance(expr, MaximumExpr):
15✔
130
            resolved = self._resolve_maximumexpr(expr)
15✔
131
        elif isinstance(expr, RangeExpr):
15✔
132
            resolved = self._resolve_rangeexpr(expr)
15✔
133
        elif isinstance(expr, Note):
15✔
134
            resolved = self._get_note(expr)
15✔
135
        elif isinstance(expr, Define):
15✔
NEW
136
            resolved = self._get_define(expr)
×
137
        else:
138
            raise ValueError(f"{expr!r} is not a valid expression.")
15✔
139
        if brackets and isinstance(expr, self._BRACKETTYPES):
15✔
140
            resolved = f"({resolved})"
15✔
141
        return resolved
15✔
142

143
    def _resolve_ident(self, ident: Ident) -> str:
15✔
144
        # Remapping of identifier, i.e. on instance port list
145
        if self.remap is not None:
15✔
146
            if ident.name in self.remap.keys():
15✔
147
                ref = self.remap[ident.name]
15✔
148
                if ref.value is not None and ref.value != ref:
15✔
149
                    # resolve remappend identifier value
150
                    return self.resolve(ref.value)
×
151
                # just use default value
152
                return self._resolve_value(ident.type_)
15✔
153

154
        # Namespace checking
155
        if self.namespace is not None:
15✔
156
            # check if identifier exists in namespace.
157
            if ident.name not in self.namespace:
15✔
158
                raise ValueError(f"{ident!r} not known within current namespace.")
15✔
159

160
        return ident.name
15✔
161

162
    def _resolve_op(self, op: Op) -> str:
15✔
163
        left = self._resolve(op.left, brackets=True)
15✔
164
        right = self._resolve(op.right, brackets=True)
15✔
165
        sign = self._opremap.get(op.sign, op.sign)
15✔
166
        return f"{left} {sign} {right}"
15✔
167

168
    def _resolve_boolop(self, op: BoolOp) -> str:
15✔
169
        left = self._resolve(op.left, brackets=True)
15✔
170
        right = self._resolve(op.right, brackets=True)
15✔
171
        return f"{left} {op.sign} {right}"
15✔
172

173
    def _resolve_sop(self, op: SOp) -> str:
15✔
174
        one = self._resolve(op.one)
15✔
175
        return f"{op.sign}{one}{op.postsign}"
15✔
176

177
    def _resolve_sliceop(self, op: SliceOp) -> str:
15✔
178
        one = self._resolve(op.one)
15✔
179
        return f"{one}{self._resolve_slice(op.slice_, opt=True)}"
15✔
180

181
    def resolve_slice(self, slice_: Slice, opt: bool = True) -> str:
15✔
182
        """Resolve Slice."""
183
        return self._resolve_slice(slice_, opt=opt)
15✔
184

185
    def _resolve_slice(self, slice_: Slice, opt: bool = False) -> str:
15✔
186
        left = slice_.left
15✔
187
        right = slice_.right
15✔
188
        if opt and left is right:
15✔
189
            return f"[{left}]"
15✔
190

191
        if not isinstance(left, int):
15✔
192
            left = self.resolve(left)
15✔
193
        if not isinstance(right, int):
15✔
194
            right = self.resolve(right)
15✔
195
        return f"[{left}:{right}]"
15✔
196

197
    def _resolve_concatexpr(self, expr: ConcatExpr) -> str:
15✔
198
        items = ", ".join(self._resolve(item) for item in expr.items)
15✔
199
        return f"{{{items}}}"
15✔
200

201
    def _resolve_ternaryexpr(self, expr: TernaryExpr) -> str:
15✔
202
        cond = self._resolve(expr.cond, brackets=True)
15✔
203
        one = self._resolve(expr.one, brackets=True)
15✔
204
        other = self._resolve(expr.other, brackets=True)
15✔
205
        return f"{cond} ? {one} : {other}"
15✔
206

207
    def _resolve_log2expr(self, expr: Log2Expr) -> str:
15✔
208
        raise NotImplementedError
209

210
    def _resolve_minimumexpr(self, expr: MinimumExpr) -> str:
15✔
211
        raise NotImplementedError
212

213
    def _resolve_maximumexpr(self, expr: MaximumExpr) -> str:
15✔
214
        raise NotImplementedError
215

216
    def _resolve_rangeexpr(self, expr: RangeExpr) -> str:
15✔
217
        raise NotImplementedError
218

219
    def _resolve_constexpr(self, expr: ConstExpr) -> str:
15✔
220
        try:
15✔
221
            return self._resolve_value(expr.type_)
15✔
222
        except ValueError as exc:
15✔
223
            raise ValueError(f"{expr} {exc}") from None
15✔
224

225
    def resolve_value(self, type_: BaseType, value=None) -> str:
15✔
226
        """Resolve Value."""
227
        return self._resolve_value(type_, value=value)
15✔
228

229
    def _resolve_value(self, type_: BaseType, value=None) -> str:  # noqa: C901, PLR0911, PLR0912
15✔
230
        if isinstance(type_, ArrayType):
15✔
231
            # TODO: value
232
            itemvalue = self._resolve_value(type_.itemtype)
15✔
233
            return self._get_array_value(itemvalue, type_.slice_)
15✔
234

235
        if not isinstance(type_, (BaseScalarType, StringType, FloatType, DoubleType)):
15✔
236
            raise ValueError(f"Cannot resolve type {type_}")
15✔
237
        if value is None:
15✔
238
            value = type_.default
15✔
239

240
        if isinstance(type_, StringType):
15✔
241
            return self._get_string_value(value)
15✔
242

243
        # None
244
        if value is None:
15✔
245
            return ""
15✔
246

247
        # Expr
248
        if isinstance(value, Expr):
15✔
249
            return self.resolve(value)
15✔
250

251
        while isinstance(type_, BaseEnumType):
15✔
252
            type_ = type_.keytype
15✔
253

254
        if isinstance(type_, BitType):
15✔
255
            return self._get_bit_value(int(value))
15✔
256

257
        if isinstance(type_, UintType):
15✔
258
            width = int(type_.width)
15✔
259
            if width < 1:
15✔
260
                raise ValueError(f"Invalid width {width}")
15✔
261
            return self._get_uint_value(value, type_.width)
15✔
262

263
        if isinstance(type_, SintType):
15✔
264
            width = int(type_.width)
15✔
265
            if width < 1:
15✔
266
                raise ValueError(f"Invalid width {width}")
15✔
267
            return self._get_sint_value(value, type_.width)
15✔
268

269
        if isinstance(type_, IntegerType):
15✔
270
            return self._get_integer_value(value)
15✔
271

272
        if isinstance(type_, RailType):
15✔
273
            return self._get_rail_value(value)
15✔
274

275
        if isinstance(type_, BoolType):
15✔
276
            return self._get_bool_value(value)
15✔
277

278
        if isinstance(type_, FloatType):
15✔
279
            return self._get_float_value(value)
15✔
280

281
        if isinstance(type_, DoubleType):
15✔
282
            return self._get_double_value(value)
15✔
283

284
        raise AssertionError
285

286
    @staticmethod
15✔
287
    def _get_rail_value(value: int) -> str:
15✔
288
        return str(value)
15✔
289

290
    @staticmethod
15✔
291
    def _get_bit_value(value: int) -> str:
15✔
292
        return str(value)
15✔
293

294
    @staticmethod
15✔
295
    def _get_uint_value(value: int, width: int | Expr) -> str:
15✔
296
        return f"0x{value:X}"
15✔
297

298
    @staticmethod
15✔
299
    def _get_sint_value(value: int, width: int | Expr) -> str:
15✔
300
        if value < 0:
15✔
301
            return f"-0x{-value:X}"
15✔
302
        return f"0x{value:X}"
15✔
303

304
    @staticmethod
15✔
305
    def _get_integer_value(value: int) -> str:
15✔
306
        return str(value)
15✔
307

308
    @staticmethod
15✔
309
    def _get_bool_value(value: bool) -> str:
15✔
310
        return str(value)
15✔
311

312
    @staticmethod
15✔
313
    def _get_string_value(value: int) -> str:
15✔
314
        return repr(value)
15✔
315

316
    @staticmethod
15✔
317
    def _get_note(note: Note) -> str:
15✔
318
        return repr(note.note)
15✔
319

320
    @staticmethod
15✔
321
    def _get_define(define: Define) -> str:
15✔
NEW
322
        return repr(define)
×
323

324
    def _get_array_value(self, itemvalue: str, slice_: Slice) -> str:
15✔
325
        raise NotImplementedError
326

327
    @staticmethod
15✔
328
    def _get_float_value(value: float) -> str:
15✔
329
        return f"{value:.1f}"
15✔
330

331
    @staticmethod
15✔
332
    def _get_double_value(value: float) -> str:
15✔
333
        return f"{value:.1f}"
15✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc