xref: /linux/tools/net/sunrpc/xdrgen/generators/union.py (revision 08989c3e551f2d16f2615c204453b76348dd6bf9)
1#!/usr/bin/env python3
2# ex: set filetype=python:
3
4"""Generate code to handle XDR unions"""
5
6from jinja2 import Environment
7
8from generators import SourceGenerator
9from generators import create_jinja2_environment, get_jinja2_template
10
11from xdr_ast import _XdrBasic, _XdrUnion, _XdrVoid, _XdrString, get_header_name
12from xdr_ast import _XdrDeclaration, _XdrCaseSpec
13from xdr_ast import public_apis, big_endian, pages_members, pages_member_maxsize
14
15
16def emit_union_declaration(environment: Environment, node: _XdrUnion) -> None:
17    """Emit one declaration pair for an XDR union type"""
18    if node.name in public_apis:
19        template = get_jinja2_template(environment, "declaration", "close")
20        print(template.render(name=node.name))
21
22
23def emit_union_switch_spec_definition(
24    environment: Environment, node: _XdrDeclaration
25) -> None:
26    """Emit a definition for an XDR union's discriminant"""
27    assert isinstance(node, _XdrBasic)
28    template = get_jinja2_template(environment, "definition", "switch_spec")
29    print(
30        template.render(
31            name=node.name,
32            type=node.spec.type_name,
33            classifier=node.spec.c_classifier,
34        )
35    )
36
37
38def emit_union_case_spec_definition(
39    environment: Environment, node: _XdrDeclaration
40) -> None:
41    """Emit a definition for an XDR union's case arm"""
42    if isinstance(node.arm, _XdrVoid):
43        return
44    if isinstance(node.arm, _XdrString):
45        type_name = "char *"
46        classifier = ""
47    else:
48        type_name = node.arm.spec.type_name
49        classifier = node.arm.spec.c_classifier
50
51    assert isinstance(node.arm, (_XdrBasic, _XdrString))
52    template = get_jinja2_template(environment, "definition", "case_spec")
53    print(
54        template.render(
55            name=node.arm.name,
56            type=type_name,
57            classifier=classifier,
58        )
59    )
60
61
62def emit_union_definition(environment: Environment, node: _XdrUnion) -> None:
63    """Emit one XDR union definition"""
64    template = get_jinja2_template(environment, "definition", "open")
65    print(template.render(name=node.name))
66
67    emit_union_switch_spec_definition(environment, node.discriminant)
68
69    for case in node.cases:
70        emit_union_case_spec_definition(environment, case)
71
72    if node.default is not None:
73        emit_union_case_spec_definition(environment, node.default)
74
75    template = get_jinja2_template(environment, "definition", "close")
76    print(template.render(name=node.name))
77
78
79def emit_union_switch_spec_decoder(
80    environment: Environment, node: _XdrDeclaration
81) -> None:
82    """Emit a decoder for an XDR union's discriminant"""
83    assert isinstance(node, _XdrBasic)
84    template = get_jinja2_template(environment, "decoder", "switch_spec")
85    print(template.render(name=node.name, type=node.spec.type_name))
86
87
88def emit_union_arm_decoder(
89    environment: Environment, node: _XdrCaseSpec
90) -> None:
91    """Emit decoder for an XDR union's arm (data only, no case/break)"""
92
93    if isinstance(node.arm, _XdrVoid):
94        return
95    if isinstance(node.arm, _XdrString):
96        type_name = "char *"
97        classifier = ""
98    else:
99        type_name = node.arm.spec.type_name
100        classifier = node.arm.spec.c_classifier
101
102    assert isinstance(node.arm, (_XdrBasic, _XdrString))
103    template = get_jinja2_template(environment, "decoder", node.arm.template)
104    print(
105        template.render(
106            name=node.arm.name,
107            type=type_name,
108            classifier=classifier,
109        )
110    )
111
112
113def emit_union_case_spec_decoder(
114    environment: Environment, node: _XdrCaseSpec, big_endian_discriminant: bool
115) -> None:
116    """Emit decoder functions for an XDR union's case arm"""
117
118    if isinstance(node.arm, _XdrVoid):
119        return
120    if isinstance(node.arm, _XdrString):
121        type_name = "char *"
122        classifier = ""
123    else:
124        type_name = node.arm.spec.type_name
125        classifier = node.arm.spec.c_classifier
126
127    if big_endian_discriminant:
128        template = get_jinja2_template(environment, "decoder", "case_spec_be")
129    else:
130        template = get_jinja2_template(environment, "decoder", "case_spec")
131    for case in node.values:
132        print(template.render(case=case))
133
134    assert isinstance(node.arm, (_XdrBasic, _XdrString))
135    template = get_jinja2_template(environment, "decoder", node.arm.template)
136    print(
137        template.render(
138            name=node.arm.name,
139            type=type_name,
140            classifier=classifier,
141        )
142    )
143
144    template = get_jinja2_template(environment, "decoder", "break")
145    print(template.render())
146
147
148def emit_union_default_spec_decoder(environment: Environment, node: _XdrUnion) -> None:
149    """Emit a decoder function for an XDR union's default arm"""
150    default_case = node.default
151
152    # Avoid a gcc warning about a default case with boolean discriminant
153    if default_case is None and node.discriminant.spec.type_name == "bool":
154        return
155
156    template = get_jinja2_template(environment, "decoder", "default_spec")
157    print(template.render())
158
159    if default_case is None or isinstance(default_case.arm, _XdrVoid):
160        template = get_jinja2_template(environment, "decoder", "break")
161        print(template.render())
162        return
163
164    assert isinstance(default_case.arm, _XdrBasic)
165    template = get_jinja2_template(environment, "decoder", default_case.arm.template)
166    print(
167        template.render(
168            name=default_case.arm.name,
169            type=default_case.arm.spec.type_name,
170            classifier=default_case.arm.spec.c_classifier,
171        )
172    )
173
174
175def emit_union_decoder(environment: Environment, node: _XdrUnion) -> None:
176    """Emit one XDR union decoder"""
177    template = get_jinja2_template(environment, "decoder", "open")
178    print(template.render(name=node.name))
179
180    # For boolean discriminants, use if statement instead of switch
181    if node.discriminant.spec.type_name == "bool":
182        template = get_jinja2_template(environment, "decoder", "bool_spec")
183        print(template.render(name=node.discriminant.name, type=node.discriminant.spec.type_name))
184
185        # Find and emit the TRUE case
186        for case in node.cases:
187            if case.values and case.values[0] == "TRUE":
188                emit_union_arm_decoder(environment, case)
189                break
190
191        template = get_jinja2_template(environment, "decoder", "close")
192        print(template.render())
193    else:
194        emit_union_switch_spec_decoder(environment, node.discriminant)
195
196        for case in node.cases:
197            emit_union_case_spec_decoder(
198                environment,
199                case,
200                node.discriminant.spec.type_name in big_endian,
201            )
202
203        emit_union_default_spec_decoder(environment, node)
204
205        template = get_jinja2_template(environment, "decoder", "close")
206        print(template.render())
207
208
209def emit_union_switch_spec_encoder(
210    environment: Environment, node: _XdrDeclaration
211) -> None:
212    """Emit an encoder for an XDR union's discriminant"""
213    assert isinstance(node, _XdrBasic)
214    template = get_jinja2_template(environment, "encoder", "switch_spec")
215    print(template.render(name=node.name, type=node.spec.type_name))
216
217
218def emit_union_pages_arm_encoder(
219    environment: Environment, union_name: str, arm: _XdrDeclaration, peer: str
220) -> bool:
221    """Emit a page-resident encoder for a "pragma pages" union arm
222
223    Return True when the arm is pages-marked and was emitted here, so
224    the caller skips the ordinary arm encoder.
225    """
226    if (union_name, arm.name) not in pages_members:
227        return False
228    if peer != "server":
229        raise NotImplementedError(
230            "pragma pages is server-side only; "
231            + peer
232            + " generation is not yet supported"
233        )
234    template = get_jinja2_template(environment, "encoder", "pages_opaque")
235    print(
236        template.render(
237            name=arm.name,
238            maxsize=pages_member_maxsize(arm),
239        )
240    )
241    return True
242
243
244def emit_union_arm_encoder(
245    environment: Environment, node: _XdrCaseSpec, union_name: str, peer: str
246) -> None:
247    """Emit encoder for an XDR union's arm (data only, no case/break)"""
248
249    if isinstance(node.arm, _XdrVoid):
250        return
251    if emit_union_pages_arm_encoder(environment, union_name, node.arm, peer):
252        return
253    if isinstance(node.arm, _XdrString):
254        type_name = "char *"
255    else:
256        type_name = node.arm.spec.type_name
257
258    assert isinstance(node.arm, (_XdrBasic, _XdrString))
259    template = get_jinja2_template(environment, "encoder", node.arm.template)
260    print(
261        template.render(
262            name=node.arm.name,
263            type=type_name,
264        )
265    )
266
267
268def emit_union_case_spec_encoder(
269    environment: Environment,
270    node: _XdrCaseSpec,
271    big_endian_discriminant: bool,
272    union_name: str,
273    peer: str,
274) -> None:
275    """Emit encoder functions for an XDR union's case arm"""
276
277    if isinstance(node.arm, _XdrVoid):
278        return
279    if isinstance(node.arm, _XdrString):
280        type_name = "char *"
281    else:
282        type_name = node.arm.spec.type_name
283    if big_endian_discriminant:
284        template = get_jinja2_template(environment, "encoder", "case_spec_be")
285    else:
286        template = get_jinja2_template(environment, "encoder", "case_spec")
287    for case in node.values:
288        print(template.render(case=case))
289
290    if not emit_union_pages_arm_encoder(environment, union_name, node.arm, peer):
291        template = get_jinja2_template(environment, "encoder", node.arm.template)
292        print(
293            template.render(
294                name=node.arm.name,
295                type=type_name,
296            )
297        )
298
299    template = get_jinja2_template(environment, "encoder", "break")
300    print(template.render())
301
302
303def emit_union_default_spec_encoder(
304    environment: Environment, node: _XdrUnion, peer: str
305) -> None:
306    """Emit an encoder function for an XDR union's default arm"""
307    default_case = node.default
308
309    # Avoid a gcc warning about a default case with boolean discriminant
310    if default_case is None and node.discriminant.spec.type_name == "bool":
311        return
312
313    template = get_jinja2_template(environment, "encoder", "default_spec")
314    print(template.render())
315
316    if default_case is None or isinstance(default_case.arm, _XdrVoid):
317        template = get_jinja2_template(environment, "encoder", "break")
318        print(template.render())
319        return
320
321    if emit_union_pages_arm_encoder(environment, node.name, default_case.arm, peer):
322        return
323
324    template = get_jinja2_template(environment, "encoder", default_case.arm.template)
325    print(
326        template.render(
327            name=default_case.arm.name,
328            type=default_case.arm.spec.type_name,
329        )
330    )
331
332
333def emit_union_encoder(environment, node: _XdrUnion, peer: str) -> None:
334    """Emit one XDR union encoder"""
335    template = get_jinja2_template(environment, "encoder", "open")
336    print(template.render(name=node.name))
337
338    # For boolean discriminants, use if statement instead of switch
339    if node.discriminant.spec.type_name == "bool":
340        template = get_jinja2_template(environment, "encoder", "bool_spec")
341        print(template.render(name=node.discriminant.name, type=node.discriminant.spec.type_name))
342
343        # Find and emit the TRUE case
344        for case in node.cases:
345            if case.values and case.values[0] == "TRUE":
346                emit_union_arm_encoder(environment, case, node.name, peer)
347                break
348
349        template = get_jinja2_template(environment, "encoder", "close")
350        print(template.render())
351    else:
352        emit_union_switch_spec_encoder(environment, node.discriminant)
353
354        for case in node.cases:
355            emit_union_case_spec_encoder(
356                environment,
357                case,
358                node.discriminant.spec.type_name in big_endian,
359                node.name,
360                peer,
361            )
362
363        emit_union_default_spec_encoder(environment, node, peer)
364
365        template = get_jinja2_template(environment, "encoder", "close")
366        print(template.render())
367
368
369def emit_union_maxsize(environment: Environment, node: _XdrUnion) -> None:
370    """Emit one maxsize macro for an XDR union type"""
371    macro_name = get_header_name().upper() + "_" + node.name + "_sz"
372    template = get_jinja2_template(environment, "maxsize", "union")
373    print(
374        template.render(
375            macro=macro_name,
376            width=" + ".join(node.symbolic_width()),
377        )
378    )
379
380
381class XdrUnionGenerator(SourceGenerator):
382    """Generate source code for XDR unions"""
383
384    def __init__(self, language: str, peer: str):
385        """Initialize an instance of this class"""
386        self.environment = create_jinja2_environment(language, "union")
387        self.peer = peer
388
389    def emit_declaration(self, node: _XdrUnion) -> None:
390        """Emit one declaration pair for an XDR union"""
391        emit_union_declaration(self.environment, node)
392
393    def emit_definition(self, node: _XdrUnion) -> None:
394        """Emit one definition for an XDR union"""
395        emit_union_definition(self.environment, node)
396
397    def emit_decoder(self, node: _XdrUnion) -> None:
398        """Emit one decoder function for an XDR union"""
399        emit_union_decoder(self.environment, node)
400
401    def emit_encoder(self, node: _XdrUnion) -> None:
402        """Emit one encoder function for an XDR union"""
403        emit_union_encoder(self.environment, node, self.peer)
404
405    def emit_maxsize(self, node: _XdrUnion) -> None:
406        """Emit one maxsize macro for an XDR union"""
407        emit_union_maxsize(self.environment, node)
408