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

FEniCS / ffcx / 20112112948

10 Dec 2025 08:19PM UTC coverage: 84.573% (+0.1%) from 84.44%
20112112948

Pull #806

github

schnellerhase
Dispatch on __call__
Pull Request #806: Backend formatter interface

155 of 167 new or added lines in 6 files covered. (92.81%)

40 existing lines in 2 files now uncovered.

4172 of 4933 relevant lines covered (84.57%)

0.85 hits per line

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

93.06
/ffcx/codegeneration/C/implementation.py
1
# Copyright (C) 2023-2025 Chris Richardson and Paul T. Kühner
2
#
3
# This file is part of FFCx. (https://www.fenicsproject.org)
4
#
5
# SPDX-License-Identifier:    LGPL-3.0-or-later
6
"""C implementation."""
7

8
import warnings
1✔
9
from functools import singledispatchmethod
1✔
10

11
import numpy as np
1✔
12
import numpy.typing as npt
1✔
13

14
import ffcx.codegeneration.lnodes as L
1✔
15
from ffcx.codegeneration.utils import dtype_to_c_type, dtype_to_scalar_dtype
1✔
16

17
math_table = {
1✔
18
    "float64": {
19
        "sqrt": "sqrt",
20
        "abs": "fabs",
21
        "cos": "cos",
22
        "sin": "sin",
23
        "tan": "tan",
24
        "acos": "acos",
25
        "asin": "asin",
26
        "atan": "atan",
27
        "cosh": "cosh",
28
        "sinh": "sinh",
29
        "tanh": "tanh",
30
        "acosh": "acosh",
31
        "asinh": "asinh",
32
        "atanh": "atanh",
33
        "power": "pow",
34
        "exp": "exp",
35
        "ln": "log",
36
        "erf": "erf",
37
        "atan_2": "atan2",
38
        "min_value": "fmin",
39
        "max_value": "fmax",
40
        "bessel_y": "yn",
41
        "bessel_j": "jn",
42
    },
43
    "float32": {
44
        "sqrt": "sqrtf",
45
        "abs": "fabsf",
46
        "cos": "cosf",
47
        "sin": "sinf",
48
        "tan": "tanf",
49
        "acos": "acosf",
50
        "asin": "asinf",
51
        "atan": "atanf",
52
        "cosh": "coshf",
53
        "sinh": "sinhf",
54
        "tanh": "tanhf",
55
        "acosh": "acoshf",
56
        "asinh": "asinhf",
57
        "atanh": "atanhf",
58
        "power": "powf",
59
        "exp": "expf",
60
        "ln": "logf",
61
        "erf": "erff",
62
        "atan_2": "atan2f",
63
        "min_value": "fminf",
64
        "max_value": "fmaxf",
65
        "bessel_y": "yn",
66
        "bessel_j": "jn",
67
    },
68
    "longdouble": {
69
        "sqrt": "sqrtl",
70
        "abs": "fabsl",
71
        "cos": "cosl",
72
        "sin": "sinl",
73
        "tan": "tanl",
74
        "acos": "acosl",
75
        "asin": "asinl",
76
        "atan": "atanl",
77
        "cosh": "coshl",
78
        "sinh": "sinhl",
79
        "tanh": "tanhl",
80
        "acosh": "acoshl",
81
        "asinh": "asinhl",
82
        "atanh": "atanhl",
83
        "power": "powl",
84
        "exp": "expl",
85
        "ln": "logl",
86
        "erf": "erfl",
87
        "atan_2": "atan2l",
88
        "min_value": "fminl",
89
        "max_value": "fmaxl",
90
    },
91
    "complex128": {
92
        "sqrt": "csqrt",
93
        "abs": "cabs",
94
        "cos": "ccos",
95
        "sin": "csin",
96
        "tan": "ctan",
97
        "acos": "cacos",
98
        "asin": "casin",
99
        "atan": "catan",
100
        "cosh": "ccosh",
101
        "sinh": "csinh",
102
        "tanh": "ctanh",
103
        "acosh": "cacosh",
104
        "asinh": "casinh",
105
        "atanh": "catanh",
106
        "power": "cpow",
107
        "exp": "cexp",
108
        "ln": "clog",
109
        "real": "creal",
110
        "imag": "cimag",
111
        "conj": "conj",
112
        "max_value": "fmax",
113
        "min_value": "fmin",
114
        "bessel_y": "yn",
115
        "bessel_j": "jn",
116
    },
117
    "complex64": {
118
        "sqrt": "csqrtf",
119
        "abs": "cabsf",
120
        "cos": "ccosf",
121
        "sin": "csinf",
122
        "tan": "ctanf",
123
        "acos": "cacosf",
124
        "asin": "casinf",
125
        "atan": "catanf",
126
        "cosh": "ccoshf",
127
        "sinh": "csinhf",
128
        "tanh": "ctanhf",
129
        "acosh": "cacoshf",
130
        "asinh": "casinhf",
131
        "atanh": "catanhf",
132
        "power": "cpowf",
133
        "exp": "cexpf",
134
        "ln": "clogf",
135
        "real": "crealf",
136
        "imag": "cimagf",
137
        "conj": "conjf",
138
        "max_value": "fmaxf",
139
        "min_value": "fminf",
140
        "bessel_y": "yn",
141
        "bessel_j": "jn",
142
    },
143
}
144

145

146
class Formatter:
1✔
147
    """C formatter."""
148

149
    scalar_type: np.dtype
1✔
150
    real_type: np.dtype
1✔
151

152
    def __init__(self, dtype: npt.DTypeLike) -> None:
1✔
153
        """Initialise."""
154
        self.scalar_type = np.dtype(dtype)
1✔
155
        self.real_type = dtype_to_scalar_dtype(dtype)
1✔
156

157
    def _dtype_to_name(self, dtype) -> str:
1✔
158
        """Convert dtype to C name."""
159
        if dtype == L.DataType.SCALAR:
1✔
160
            return dtype_to_c_type(self.scalar_type)
1✔
161
        if dtype == L.DataType.REAL:
1✔
162
            return dtype_to_c_type(self.real_type)
1✔
163
        if dtype == L.DataType.INT:
1✔
UNCOV
164
            return "int"
×
165
        if dtype == L.DataType.BOOL:
1✔
166
            return "bool"
1✔
UNCOV
167
        raise ValueError(f"Invalid dtype: {dtype}")
×
168

169
    def _format_number(self, x):
1✔
170
        """Format a number."""
171
        # Use 16sf for precision (good for float64 or less)
172
        if isinstance(x, complex):
1✔
173
            return f"({x.real:.16}+I*{x.imag:.16})"
1✔
174
        elif isinstance(x, float):
1✔
175
            return f"{x:.16}"
1✔
176
        return str(x)
1✔
177

178
    def _build_initializer_lists(self, values):
1✔
179
        """Build initializer lists."""
180
        arr = "{"
1✔
181
        if len(values.shape) == 1:
1✔
182
            arr += ", ".join(self._format_number(v) for v in values)
1✔
183
        elif len(values.shape) > 1:
1✔
184
            arr += ",\n  ".join(self._build_initializer_lists(v) for v in values)
1✔
185
        arr += "}"
1✔
186
        return arr
1✔
187

188
    @singledispatchmethod
1✔
189
    def __call__(self, obj) -> str:
1✔
190
        """Format an L Node."""
NEW
UNCOV
191
        raise NotImplementedError(f"Can not format objce to type {type(obj)}")
×
192
        # return self._format(obj)
193

194
    @__call__.register
1✔
195
    def _(self, slist: L.StatementList) -> str:
1✔
196
        """Format a statement list."""
NEW
UNCOV
197
        return "".join(self(s) for s in slist.statements)
×
198

199
    @__call__.register
1✔
200
    def _(self, slist: L.StatementList) -> str:
1✔
201
        """Format a statement list."""
202
        return "".join(self(s) for s in slist.statements)
1✔
203

204
    @__call__.register
1✔
205
    def _(self, section: L.Section) -> str:
1✔
206
        """Format a section."""
207
        # add new line before section
208
        comments = (
1✔
209
            f"// ------------------------ \n"
210
            f"// Section: {section.name}\n"
211
            f"// Inputs: {', '.join(w.name for w in section.input)}\n"
212
            f"// Outputs: {', '.join(w.name for w in section.output)}\n"
213
        )
214
        declarations = "".join(self(s) for s in section.declarations)
1✔
215

216
        body = ""
1✔
217
        if len(section.statements) > 0:
1✔
218
            declarations += "{\n  "
1✔
219
            body = "".join(self(s) for s in section.statements)
1✔
220
            body = body.replace("\n", "\n  ")
1✔
221
            body = body[:-2] + "}\n"
1✔
222

223
        body += "// ------------------------ \n"
1✔
224
        return str(comments + declarations + body)
1✔
225

226
    @__call__.register
1✔
227
    def _(self, c: L.Comment) -> str:
1✔
228
        """Format a comment."""
229
        return "// " + c.comment + "\n"
1✔
230

231
    @__call__.register
1✔
232
    def _(self, arr: L.ArrayDecl) -> str:
1✔
233
        """Format an array declaration."""
234
        dtype = arr.symbol.dtype
1✔
235
        typename = self._dtype_to_name(dtype)
1✔
236

237
        symbol = self(arr.symbol)
1✔
238
        dims = "".join([f"[{i}]" for i in arr.sizes])
1✔
239
        if arr.values is None:
1✔
UNCOV
240
            assert arr.const is False
×
UNCOV
241
            return f"{typename} {symbol}{dims};\n"
×
242

243
        vals = self._build_initializer_lists(arr.values)
1✔
244
        cstr = "static const " if arr.const else ""
1✔
245
        return f"{cstr}{typename} {symbol}{dims} = {vals};\n"
1✔
246

247
    @__call__.register
1✔
248
    def _(self, arr: L.ArrayAccess) -> str:
1✔
249
        """Format an array access."""
250
        name = self(arr.array)
1✔
251
        indices = f"[{']['.join(self(i) for i in arr.indices)}]"
1✔
252
        return f"{name}{indices}"
1✔
253

254
    @__call__.register
1✔
255
    def _(self, v: L.VariableDecl) -> str:
1✔
256
        """Format a variable declaration."""
257
        val = self(v.value)
1✔
258
        symbol = self(v.symbol)
1✔
259
        typename = self._dtype_to_name(v.symbol.dtype)
1✔
260
        return f"{typename} {symbol} = {val};\n"
1✔
261

262
    @__call__.register
1✔
263
    def _(self, oper: L.NaryOp) -> str:
1✔
264
        """Format an n-ary operation."""
265
        # Format children
266
        args = [self(arg) for arg in oper.args]
1✔
267

268
        # Apply parentheses
269
        for i in range(len(args)):
1✔
270
            if oper.args[i].precedence >= oper.precedence:
1✔
271
                args[i] = "(" + args[i] + ")"
1✔
272

273
        # Return combined string
274
        return f" {oper.op} ".join(args)
1✔
275

276
    @__call__.register
1✔
277
    def _(self, oper: L.BinOp) -> str:
1✔
278
        """Format a binary operation."""
279
        # Format children
280
        lhs = self(oper.lhs)
1✔
281
        rhs = self(oper.rhs)
1✔
282

283
        # Apply parentheses
284
        if oper.lhs.precedence >= oper.precedence:
1✔
285
            lhs = f"({lhs})"
1✔
286
        if oper.rhs.precedence >= oper.precedence:
1✔
287
            rhs = f"({rhs})"
1✔
288

289
        # Return combined string
290
        return f"{lhs} {oper.op} {rhs}"
1✔
291

292
    def _format_unary_op(self, oper) -> str:
1✔
293
        """Format a unary operation."""
294
        arg = self(oper.arg)
1✔
295
        if oper.arg.precedence >= oper.precedence:
1✔
UNCOV
296
            return f"{oper.op}({arg})"
×
297
        return f"{oper.op}{arg}"
1✔
298

299
    @__call__.register
1✔
300
    def _(self, val: L.Neg) -> str:
1✔
301
        return self._format_unary_op(val)
1✔
302

303
    @__call__.register
1✔
304
    def _(self, val: L.Not) -> str:
1✔
NEW
UNCOV
305
        return self._format_unary_op(val)
×
306

307
    @__call__.register
1✔
308
    def _(self, val: L.LiteralFloat) -> str:
1✔
309
        """Format a literal float."""
310
        value = self._format_number(val.value)
1✔
311
        return f"{value}"
1✔
312

313
    @__call__.register
1✔
314
    def _(self, val: L.LiteralInt) -> str:
1✔
315
        """Format a literal int."""
316
        return f"{val.value}"
1✔
317

318
    @__call__.register
1✔
319
    def _(self, r: L.ForRange) -> str:
1✔
320
        """Format a for loop over a range."""
321
        begin = self(r.begin)
1✔
322
        end = self(r.end)
1✔
323
        index = self(r.index)
1✔
324
        output = f"for (int {index} = {begin}; {index} < {end}; ++{index})\n"
1✔
325
        output += "{\n"
1✔
326
        body = self(r.body)
1✔
327
        for line in body.split("\n"):
1✔
328
            if len(line) > 0:
1✔
329
                output += f"  {line}\n"
1✔
330
        output += "}\n"
1✔
331
        return output
1✔
332

333
    @__call__.register
1✔
334
    def _(self, s: L.Statement) -> str:
1✔
335
        """Format a statement."""
336
        return self(s.expr)
1✔
337

338
    def _format_assign(self, expr) -> str:
1✔
339
        """Format an assignment."""
340
        rhs = self(expr.rhs)
1✔
341
        lhs = self(expr.lhs)
1✔
342
        return f"{lhs} {expr.op} {rhs};\n"
1✔
343

344
    @__call__.register
1✔
345
    def _(self, expr: L.Assign) -> str:
1✔
346
        return self._format_assign(expr)
1✔
347

348
    @__call__.register
1✔
349
    def _(self, expr: L.AssignAdd) -> str:
1✔
350
        return self._format_assign(expr)
1✔
351

352
    @__call__.register
1✔
353
    def _(self, s: L.Conditional) -> str:
1✔
354
        """Format a conditional."""
355
        # Format children
356
        c = self(s.condition)
1✔
357
        t = self(s.true)
1✔
358
        f = self(s.false)
1✔
359

360
        # Apply parentheses
361
        if s.condition.precedence >= s.precedence:
1✔
UNCOV
362
            c = "(" + c + ")"
×
363
        if s.true.precedence >= s.precedence:
1✔
UNCOV
364
            t = "(" + t + ")"
×
365
        if s.false.precedence >= s.precedence:
1✔
UNCOV
366
            f = "(" + f + ")"
×
367

368
        # Return combined string
369
        return c + " ? " + t + " : " + f
1✔
370

371
    @__call__.register
1✔
372
    def _(self, s: L.Symbol) -> str:
1✔
373
        """Format a symbol."""
374
        return f"{s.name}"
1✔
375

376
    @__call__.register
1✔
377
    def _(self, mi: L.MultiIndex) -> str:
1✔
378
        """Format a multi-index."""
379
        return self(mi.global_index)
1✔
380

381
    @__call__.register
1✔
382
    def _(self, c: L.MathFunction) -> str:
1✔
383
        """Format a mathematical function."""
384
        # Get a table of functions for this type, if available
385
        arg_type = self.scalar_type
1✔
386
        if hasattr(c.args[0], "dtype"):
1✔
387
            if c.args[0].dtype == L.DataType.REAL:
1✔
388
                arg_type = self.real_type
1✔
389
        else:
UNCOV
390
            warnings.warn(f"Syntax item without dtype {c.args[0]}")
×
391

392
        dtype_math_table = math_table[arg_type.name]
1✔
393

394
        # Get a function from the table, if available, else just use bare name
395
        func = dtype_math_table.get(c.function, c.function)
1✔
396
        args = ", ".join(self(arg) for arg in c.args)
1✔
397
        return f"{func}({args})"
1✔
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