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

FEniCS / ffcx / 20112426700

10 Dec 2025 08:31PM UTC coverage: 84.612% (+0.2%) from 84.44%
20112426700

Pull #806

github

schnellerhase
Missed one
Pull Request #806: Backend formatter interface

140 of 148 new or added lines in 6 files covered. (94.59%)

33 existing lines in 2 files now uncovered.

4157 of 4913 relevant lines covered (84.61%)

0.85 hits per line

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

93.83
/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: L.LNode) -> str:
1✔
190
        """Format an L Node."""
NEW
UNCOV
191
        raise NotImplementedError(f"Can not format object to type {type(obj)}")
×
192

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

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

210
        body = ""
1✔
211
        if len(section.statements) > 0:
1✔
212
            declarations += "{\n  "
1✔
213
            body = "".join(self(s) for s in section.statements)
1✔
214
            body = body.replace("\n", "\n  ")
1✔
215
            body = body[:-2] + "}\n"
1✔
216

217
        body += "// ------------------------ \n"
1✔
218
        return str(comments + declarations + body)
1✔
219

220
    @__call__.register
1✔
221
    def _(self, c: L.Comment) -> str:
1✔
222
        """Format a comment."""
223
        return "// " + c.comment + "\n"
1✔
224

225
    @__call__.register
1✔
226
    def _(self, arr: L.ArrayDecl) -> str:
1✔
227
        """Format an array declaration."""
228
        dtype = arr.symbol.dtype
1✔
229
        typename = self._dtype_to_name(dtype)
1✔
230

231
        symbol = self(arr.symbol)
1✔
232
        dims = "".join([f"[{i}]" for i in arr.sizes])
1✔
233
        if arr.values is None:
1✔
UNCOV
234
            assert arr.const is False
×
UNCOV
235
            return f"{typename} {symbol}{dims};\n"
×
236

237
        vals = self._build_initializer_lists(arr.values)
1✔
238
        cstr = "static const " if arr.const else ""
1✔
239
        return f"{cstr}{typename} {symbol}{dims} = {vals};\n"
1✔
240

241
    @__call__.register
1✔
242
    def _(self, arr: L.ArrayAccess) -> str:
1✔
243
        """Format an array access."""
244
        name = self(arr.array)
1✔
245
        indices = f"[{']['.join(self(i) for i in arr.indices)}]"
1✔
246
        return f"{name}{indices}"
1✔
247

248
    @__call__.register
1✔
249
    def _(self, v: L.VariableDecl) -> str:
1✔
250
        """Format a variable declaration."""
251
        val = self(v.value)
1✔
252
        symbol = self(v.symbol)
1✔
253
        typename = self._dtype_to_name(v.symbol.dtype)
1✔
254
        return f"{typename} {symbol} = {val};\n"
1✔
255

256
    @__call__.register
1✔
257
    def _(self, oper: L.NaryOp) -> str:
1✔
258
        """Format an n-ary operation."""
259
        # Format children
260
        args = [self(arg) for arg in oper.args]
1✔
261

262
        # Apply parentheses
263
        for i in range(len(args)):
1✔
264
            if oper.args[i].precedence >= oper.precedence:
1✔
265
                args[i] = "(" + args[i] + ")"
1✔
266

267
        # Return combined string
268
        return f" {oper.op} ".join(args)
1✔
269

270
    @__call__.register
1✔
271
    def _(self, oper: L.BinOp) -> str:
1✔
272
        """Format a binary operation."""
273
        # Format children
274
        lhs = self(oper.lhs)
1✔
275
        rhs = self(oper.rhs)
1✔
276

277
        # Apply parentheses
278
        if oper.lhs.precedence >= oper.precedence:
1✔
279
            lhs = f"({lhs})"
1✔
280
        if oper.rhs.precedence >= oper.precedence:
1✔
281
            rhs = f"({rhs})"
1✔
282

283
        # Return combined string
284
        return f"{lhs} {oper.op} {rhs}"
1✔
285

286
    @__call__.register(L.Neg)
1✔
287
    @__call__.register(L.Not)
1✔
288
    def _(self, oper) -> str:
1✔
289
        """Format a unary operation."""
290
        arg = self(oper.arg)
1✔
291
        if oper.arg.precedence >= oper.precedence:
1✔
UNCOV
292
            return f"{oper.op}({arg})"
×
293
        return f"{oper.op}{arg}"
1✔
294

295
    @__call__.register
1✔
296
    def _(self, val: L.LiteralFloat) -> str:
1✔
297
        """Format a literal float."""
298
        value = self._format_number(val.value)
1✔
299
        return f"{value}"
1✔
300

301
    @__call__.register
1✔
302
    def _(self, val: L.LiteralInt) -> str:
1✔
303
        """Format a literal int."""
304
        return f"{val.value}"
1✔
305

306
    @__call__.register
1✔
307
    def _(self, r: L.ForRange) -> str:
1✔
308
        """Format a for loop over a range."""
309
        begin = self(r.begin)
1✔
310
        end = self(r.end)
1✔
311
        index = self(r.index)
1✔
312
        output = f"for (int {index} = {begin}; {index} < {end}; ++{index})\n"
1✔
313
        output += "{\n"
1✔
314
        body = self(r.body)
1✔
315
        for line in body.split("\n"):
1✔
316
            if len(line) > 0:
1✔
317
                output += f"  {line}\n"
1✔
318
        output += "}\n"
1✔
319
        return output
1✔
320

321
    @__call__.register
1✔
322
    def _(self, s: L.Statement) -> str:
1✔
323
        """Format a statement."""
324
        return self(s.expr)
1✔
325

326
    @__call__.register(L.Assign)
1✔
327
    @__call__.register(L.AssignAdd)
1✔
328
    def _(self, expr: L.Assign | L.AssignAdd) -> str:
1✔
329
        """Format an assignment."""
330
        rhs = self(expr.rhs)
1✔
331
        lhs = self(expr.lhs)
1✔
332
        return f"{lhs} {expr.op} {rhs};\n"
1✔
333

334
    @__call__.register
1✔
335
    def _(self, s: L.Conditional) -> str:
1✔
336
        """Format a conditional."""
337
        # Format children
338
        c = self(s.condition)
1✔
339
        t = self(s.true)
1✔
340
        f = self(s.false)
1✔
341

342
        # Apply parentheses
343
        if s.condition.precedence >= s.precedence:
1✔
UNCOV
344
            c = "(" + c + ")"
×
345
        if s.true.precedence >= s.precedence:
1✔
346
            t = "(" + t + ")"
×
347
        if s.false.precedence >= s.precedence:
1✔
UNCOV
348
            f = "(" + f + ")"
×
349

350
        # Return combined string
351
        return c + " ? " + t + " : " + f
1✔
352

353
    @__call__.register
1✔
354
    def _(self, s: L.Symbol) -> str:
1✔
355
        """Format a symbol."""
356
        return f"{s.name}"
1✔
357

358
    @__call__.register
1✔
359
    def _(self, mi: L.MultiIndex) -> str:
1✔
360
        """Format a multi-index."""
361
        return self(mi.global_index)
1✔
362

363
    @__call__.register
1✔
364
    def _(self, c: L.MathFunction) -> str:
1✔
365
        """Format a mathematical function."""
366
        # Get a table of functions for this type, if available
367
        arg_type = self.scalar_type
1✔
368
        if hasattr(c.args[0], "dtype"):
1✔
369
            if c.args[0].dtype == L.DataType.REAL:
1✔
370
                arg_type = self.real_type
1✔
371
        else:
UNCOV
372
            warnings.warn(f"Syntax item without dtype {c.args[0]}")
×
373

374
        dtype_math_table = math_table[arg_type.name]
1✔
375

376
        # Get a function from the table, if available, else just use bare name
377
        func = dtype_math_table.get(c.function, c.function)
1✔
378
        args = ", ".join(self(arg) for arg in c.args)
1✔
379
        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