xref: /linux/tools/net/sunrpc/xdrgen/xdr_ast.py (revision d141ec2825b4d3ec52f27c43bdd864090159273a)
1#!/usr/bin/env python3
2# ex: set filetype=python:
3
4"""Define and implement the Abstract Syntax Tree for the XDR language."""
5
6import sys
7from typing import List
8from dataclasses import dataclass, KW_ONLY
9
10from lark import ast_utils, Transformer
11from lark.tree import Meta
12
13this_module = sys.modules[__name__]
14
15big_endian = []
16excluded_apis = []
17header_name = "none"
18public_apis = []
19structs = set()
20pass_by_reference = set()
21
22constants = {}
23
24
25def xdr_quadlen(val: str) -> int:
26    """Return integer XDR width of an XDR type"""
27    if val in constants:
28        octets = constants[val]
29    else:
30        octets = int(val)
31    return int((octets + 3) / 4)
32
33
34symbolic_widths = {
35    "void": ["XDR_void"],
36    "bool": ["XDR_bool"],
37    "short": ["XDR_short"],
38    "unsigned_short": ["XDR_unsigned_short"],
39    "int": ["XDR_int"],
40    "unsigned_int": ["XDR_unsigned_int"],
41    "long": ["XDR_long"],
42    "unsigned_long": ["XDR_unsigned_long"],
43    "hyper": ["XDR_hyper"],
44    "unsigned_hyper": ["XDR_unsigned_hyper"],
45}
46
47# Numeric XDR widths are tracked in a dictionary that is keyed
48# by type_name because sometimes a caller has nothing more than
49# the type_name to use to figure out the numeric width.
50max_widths = {
51    "void": 0,
52    "bool": 1,
53    "short": 1,
54    "unsigned_short": 1,
55    "int": 1,
56    "unsigned_int": 1,
57    "long": 1,
58    "unsigned_long": 1,
59    "hyper": 2,
60    "unsigned_hyper": 2,
61}
62
63
64@dataclass
65class _XdrAst(ast_utils.Ast):
66    """Base class for the XDR abstract syntax tree"""
67
68    # Source position of the construct's declared identifier, when
69    # the transformer records one, so semantic diagnostics can point
70    # at the exact declaration. The KW_ONLY marker makes the fields
71    # keyword-only, so they never disturb the positional child
72    # ordering lark uses to build each node; 0 means the position was
73    # not recorded.
74    _: KW_ONLY
75    line: int = 0
76    column: int = 0
77
78
79@dataclass
80class _XdrIdentifier(_XdrAst):
81    """Corresponds to 'identifier' in the XDR language grammar"""
82
83    symbol: str
84
85
86@dataclass
87class _XdrValue(_XdrAst):
88    """Corresponds to 'value' in the XDR language grammar"""
89
90    value: str
91
92
93@dataclass
94class _XdrConstantValue(_XdrAst):
95    """Corresponds to 'constant' in the XDR language grammar"""
96
97    value: int
98
99
100@dataclass
101class _XdrTypeSpecifier(_XdrAst):
102    """Corresponds to 'type_specifier' in the XDR language grammar"""
103
104    type_name: str
105    c_classifier: str = ""
106
107
108@dataclass
109class _XdrDefinedType(_XdrTypeSpecifier):
110    """Corresponds to a type defined by the input specification"""
111
112    def symbolic_width(self) -> List:
113        """Return list containing XDR width of type's components"""
114        return [get_header_name().upper() + "_" + self.type_name + "_sz"]
115
116    def __post_init__(self):
117        if self.type_name in structs:
118            self.c_classifier = "struct "
119        symbolic_widths[self.type_name] = self.symbolic_width()
120
121
122@dataclass
123class _XdrBuiltInType(_XdrTypeSpecifier):
124    """Corresponds to a built-in XDR type"""
125
126    def symbolic_width(self) -> List:
127        """Return list containing XDR width of type's components"""
128        return symbolic_widths[self.type_name]
129
130
131@dataclass
132class _XdrDeclaration(_XdrAst):
133    """Base class of XDR type declarations"""
134
135
136@dataclass
137class _XdrFixedLengthOpaque(_XdrDeclaration):
138    """A fixed-length opaque declaration"""
139
140    name: str
141    size: str
142    template: str = "fixed_length_opaque"
143
144    def max_width(self) -> int:
145        """Return width of type in XDR_UNITS"""
146        return xdr_quadlen(self.size)
147
148    def symbolic_width(self) -> List:
149        """Return list containing XDR width of type's components"""
150        return ["XDR_QUADLEN(" + self.size + ")"]
151
152    def __post_init__(self):
153        max_widths[self.name] = self.max_width()
154        symbolic_widths[self.name] = self.symbolic_width()
155
156
157@dataclass
158class _XdrVariableLengthOpaque(_XdrDeclaration):
159    """A variable-length opaque declaration"""
160
161    name: str
162    maxsize: str
163    template: str = "variable_length_opaque"
164
165    def max_width(self) -> int:
166        """Return width of type in XDR_UNITS"""
167        return 1 + xdr_quadlen(self.maxsize)
168
169    def symbolic_width(self) -> List:
170        """Return list containing XDR width of type's components"""
171        widths = ["XDR_unsigned_int"]
172        if self.maxsize != "0":
173            widths.append("XDR_QUADLEN(" + self.maxsize + ")")
174        return widths
175
176    def __post_init__(self):
177        max_widths[self.name] = self.max_width()
178        symbolic_widths[self.name] = self.symbolic_width()
179
180
181@dataclass
182class _XdrString(_XdrDeclaration):
183    """A (NUL-terminated) variable-length string declaration"""
184
185    name: str
186    maxsize: str
187    template: str = "string"
188
189    def max_width(self) -> int:
190        """Return width of type in XDR_UNITS"""
191        return 1 + xdr_quadlen(self.maxsize)
192
193    def symbolic_width(self) -> List:
194        """Return list containing XDR width of type's components"""
195        widths = ["XDR_unsigned_int"]
196        if self.maxsize != "0":
197            widths.append("XDR_QUADLEN(" + self.maxsize + ")")
198        return widths
199
200    def __post_init__(self):
201        max_widths[self.name] = self.max_width()
202        symbolic_widths[self.name] = self.symbolic_width()
203
204
205@dataclass
206class _XdrFixedLengthArray(_XdrDeclaration):
207    """A fixed-length array declaration"""
208
209    name: str
210    spec: _XdrTypeSpecifier
211    size: str
212    template: str = "fixed_length_array"
213
214    def max_width(self) -> int:
215        """Return width of type in XDR_UNITS"""
216        return xdr_quadlen(self.size) * max_widths[self.spec.type_name]
217
218    def symbolic_width(self) -> List:
219        """Return list containing XDR width of type's components"""
220        item_width = " + ".join(symbolic_widths[self.spec.type_name])
221        return ["(" + self.size + " * (" + item_width + "))"]
222
223    def __post_init__(self):
224        max_widths[self.name] = self.max_width()
225        symbolic_widths[self.name] = self.symbolic_width()
226
227
228@dataclass
229class _XdrVariableLengthArray(_XdrDeclaration):
230    """A variable-length array declaration"""
231
232    name: str
233    spec: _XdrTypeSpecifier
234    maxsize: str
235    template: str = "variable_length_array"
236
237    def max_width(self) -> int:
238        """Return width of type in XDR_UNITS"""
239        return 1 + (xdr_quadlen(self.maxsize) * max_widths[self.spec.type_name])
240
241    def symbolic_width(self) -> List:
242        """Return list containing XDR width of type's components"""
243        widths = ["XDR_unsigned_int"]
244        if self.maxsize != "0":
245            item_width = " + ".join(symbolic_widths[self.spec.type_name])
246            widths.append("(" + self.maxsize + " * (" + item_width + "))")
247        return widths
248
249    def __post_init__(self):
250        max_widths[self.name] = self.max_width()
251        symbolic_widths[self.name] = self.symbolic_width()
252
253
254@dataclass
255class _XdrOptionalData(_XdrDeclaration):
256    """An 'optional_data' declaration"""
257
258    name: str
259    spec: _XdrTypeSpecifier
260    template: str = "optional_data"
261
262    def max_width(self) -> int:
263        """Return width of type in XDR_UNITS"""
264        return 1
265
266    def symbolic_width(self) -> List:
267        """Return list containing XDR width of type's components"""
268        return ["XDR_bool"]
269
270    def __post_init__(self):
271        structs.add(self.name)
272        pass_by_reference.add(self.name)
273        max_widths[self.name] = self.max_width()
274        symbolic_widths[self.name] = self.symbolic_width()
275
276
277@dataclass
278class _XdrBasic(_XdrDeclaration):
279    """A 'basic' declaration"""
280
281    name: str
282    spec: _XdrTypeSpecifier
283    template: str = "basic"
284
285    def max_width(self) -> int:
286        """Return width of type in XDR_UNITS"""
287        return max_widths[self.spec.type_name]
288
289    def symbolic_width(self) -> List:
290        """Return list containing XDR width of type's components"""
291        return symbolic_widths[self.spec.type_name]
292
293    def __post_init__(self):
294        max_widths[self.name] = self.max_width()
295        symbolic_widths[self.name] = self.symbolic_width()
296
297
298@dataclass
299class _XdrVoid(_XdrDeclaration):
300    """A void declaration"""
301
302    name: str = "void"
303    template: str = "void"
304
305    def max_width(self) -> int:
306        """Return width of type in XDR_UNITS"""
307        return 0
308
309    def symbolic_width(self) -> List:
310        """Return list containing XDR width of type's components"""
311        return []
312
313
314@dataclass
315class _XdrConstant(_XdrAst):
316    """Corresponds to 'constant_def' in the grammar"""
317
318    name: str
319    value: str
320
321    def __post_init__(self):
322        if self.value not in constants:
323            constants[self.name] = int(self.value, 0)
324
325
326@dataclass
327class _XdrEnumerator(_XdrAst):
328    """An 'identifier = value' enumerator"""
329
330    name: str
331    value: str
332
333    def __post_init__(self):
334        if self.value not in constants:
335            constants[self.name] = int(self.value, 0)
336
337
338@dataclass
339class _XdrEnum(_XdrAst):
340    """An XDR enum definition"""
341
342    name: str
343    enumerators: List[_XdrEnumerator]
344
345    def max_width(self) -> int:
346        """Return width of type in XDR_UNITS"""
347        return 1
348
349    def symbolic_width(self) -> List:
350        """Return list containing XDR width of type's components"""
351        return ["XDR_int"]
352
353    def __post_init__(self):
354        max_widths[self.name] = self.max_width()
355        symbolic_widths[self.name] = self.symbolic_width()
356
357
358@dataclass
359class _XdrStruct(_XdrAst):
360    """An XDR struct definition"""
361
362    name: str
363    fields: List[_XdrDeclaration]
364
365    def max_width(self) -> int:
366        """Return width of type in XDR_UNITS"""
367        width = 0
368        for field in self.fields:
369            width += field.max_width()
370        return width
371
372    def symbolic_width(self) -> List:
373        """Return list containing XDR width of type's components"""
374        widths = []
375        for field in self.fields:
376            widths += field.symbolic_width()
377        return widths
378
379    def __post_init__(self):
380        structs.add(self.name)
381        pass_by_reference.add(self.name)
382        max_widths[self.name] = self.max_width()
383        symbolic_widths[self.name] = self.symbolic_width()
384
385
386@dataclass
387class _XdrPointer(_XdrAst):
388    """An XDR pointer definition"""
389
390    name: str
391    fields: List[_XdrDeclaration]
392
393    def max_width(self) -> int:
394        """Return width of type in XDR_UNITS"""
395        width = 1
396        for field in self.fields[0:-1]:
397            width += field.max_width()
398        return width
399
400    def symbolic_width(self) -> List:
401        """Return list containing XDR width of type's components"""
402        widths = []
403        widths += ["XDR_bool"]
404        for field in self.fields[0:-1]:
405            widths += field.symbolic_width()
406        return widths
407
408    def __post_init__(self):
409        structs.add(self.name)
410        pass_by_reference.add(self.name)
411        max_widths[self.name] = self.max_width()
412        symbolic_widths[self.name] = self.symbolic_width()
413
414
415@dataclass
416class _XdrTypedef(_XdrAst):
417    """An XDR typedef"""
418
419    declaration: _XdrDeclaration
420
421    def max_width(self) -> int:
422        """Return width of type in XDR_UNITS"""
423        return self.declaration.max_width()
424
425    def symbolic_width(self) -> List:
426        """Return list containing XDR width of type's components"""
427        return self.declaration.symbolic_width()
428
429    def __post_init__(self):
430        if isinstance(self.declaration, _XdrBasic):
431            new_type = self.declaration
432            if isinstance(new_type.spec, _XdrDefinedType):
433                if new_type.spec.type_name in pass_by_reference:
434                    pass_by_reference.add(new_type.name)
435                max_widths[new_type.name] = self.max_width()
436                symbolic_widths[new_type.name] = self.symbolic_width()
437
438
439@dataclass
440class _XdrCaseSpec(_XdrAst):
441    """One case in an XDR union"""
442
443    values: List[str]
444    arm: _XdrDeclaration
445    template: str = "case_spec"
446
447
448@dataclass
449class _XdrDefaultSpec(_XdrAst):
450    """Default case in an XDR union"""
451
452    arm: _XdrDeclaration
453    template: str = "default_spec"
454
455
456@dataclass
457class _XdrUnion(_XdrAst):
458    """An XDR union"""
459
460    name: str
461    discriminant: _XdrDeclaration
462    cases: List[_XdrCaseSpec]
463    default: _XdrDeclaration
464
465    def max_width(self) -> int:
466        """Return width of type in XDR_UNITS"""
467        max_width = 0
468        for case in self.cases:
469            if case.arm.max_width() > max_width:
470                max_width = case.arm.max_width()
471        if self.default:
472            if self.default.arm.max_width() > max_width:
473                max_width = self.default.arm.max_width()
474        return 1 + max_width
475
476    def symbolic_width(self) -> List:
477        """Return list containing XDR width of type's components"""
478        max_width = 0
479        for case in self.cases:
480            if case.arm.max_width() > max_width:
481                max_width = case.arm.max_width()
482                width = case.arm.symbolic_width()
483        if self.default:
484            if self.default.arm.max_width() > max_width:
485                max_width = self.default.arm.max_width()
486                width = self.default.arm.symbolic_width()
487        return symbolic_widths[self.discriminant.name] + width
488
489    def __post_init__(self):
490        structs.add(self.name)
491        pass_by_reference.add(self.name)
492        max_widths[self.name] = self.max_width()
493        symbolic_widths[self.name] = self.symbolic_width()
494
495
496@dataclass
497class _RpcProcedure(_XdrAst):
498    """RPC procedure definition"""
499
500    name: str
501    number: int
502    argument: _XdrTypeSpecifier
503    result: _XdrTypeSpecifier
504
505
506@dataclass
507class _RpcVersion(_XdrAst):
508    """RPC version definition"""
509
510    name: str
511    number: int
512    procedures: List[_RpcProcedure]
513
514
515@dataclass
516class _RpcProgram(_XdrAst):
517    """RPC program definition"""
518
519    name: str
520    number: int
521    versions: List[_RpcVersion]
522
523
524@dataclass
525class _Pragma(_XdrAst):
526    """Empty class for pragma directives"""
527
528
529@dataclass
530class _XdrPassthru(_XdrAst):
531    """Passthrough line to emit verbatim in output"""
532
533    content: str
534
535
536@dataclass
537class Definition(_XdrAst, ast_utils.WithMeta):
538    """Corresponds to 'definition' in the grammar"""
539
540    meta: Meta
541    value: _XdrAst
542
543
544@dataclass
545class Specification(_XdrAst, ast_utils.AsList):
546    """Corresponds to 'specification' in the grammar"""
547
548    definitions: List[Definition]
549
550
551class ParseToAst(Transformer):
552    """Functions that transform productions into AST nodes"""
553
554    def identifier(self, children):
555        """Instantiate one _XdrIdentifier object"""
556        token = children[0]
557        return _XdrIdentifier(token.value, line=token.line, column=token.column)
558
559    def value(self, children):
560        """Instantiate one _XdrValue object"""
561        if isinstance(children[0], _XdrIdentifier):
562            return _XdrValue(children[0].symbol)
563        return _XdrValue(children[0].children[0].value)
564
565    def constant(self, children):
566        """Instantiate one _XdrConstantValue object"""
567        match children[0].data:
568            case "decimal_constant":
569                value = int(children[0].children[0].value, base=10)
570            case "hexadecimal_constant":
571                value = int(children[0].children[0].value, base=16)
572            case "octal_constant":
573                value = int(children[0].children[0].value, base=8)
574        return _XdrConstantValue(value)
575
576    def type_specifier(self, children):
577        """Instantiate one _XdrTypeSpecifier object"""
578        if isinstance(children[0], _XdrIdentifier):
579            name = children[0].symbol
580            return _XdrDefinedType(type_name=name)
581
582        name = children[0].data.value
583        return _XdrBuiltInType(type_name=name)
584
585    def constant_def(self, children):
586        """Instantiate one _XdrConstant object"""
587        ident = children[0]
588        value = children[1].value
589        return _XdrConstant(ident.symbol, value, line=ident.line, column=ident.column)
590
591    def enum(self, children):
592        """Instantiate one _XdrEnum object"""
593        name_ident = children[0]
594
595        i = 0
596        enumerators = []
597        body = children[1]
598        while i < len(body.children):
599            ident = body.children[i]
600            value = body.children[i + 1].value
601            enumerators.append(
602                _XdrEnumerator(
603                    ident.symbol, value, line=ident.line, column=ident.column
604                )
605            )
606            i = i + 2
607
608        return _XdrEnum(
609            name_ident.symbol,
610            enumerators,
611            line=name_ident.line,
612            column=name_ident.column,
613        )
614
615    def fixed_length_opaque(self, children):
616        """Instantiate one _XdrFixedLengthOpaque declaration object"""
617        ident = children[0]
618        size = children[1].value
619
620        return _XdrFixedLengthOpaque(
621            ident.symbol, size, line=ident.line, column=ident.column
622        )
623
624    def variable_length_opaque(self, children):
625        """Instantiate one _XdrVariableLengthOpaque declaration object"""
626        ident = children[0]
627        if children[1] is not None:
628            maxsize = children[1].value
629        else:
630            maxsize = "0"
631
632        return _XdrVariableLengthOpaque(
633            ident.symbol, maxsize, line=ident.line, column=ident.column
634        )
635
636    def string(self, children):
637        """Instantiate one _XdrString declaration object"""
638        ident = children[0]
639        if children[1] is not None:
640            maxsize = children[1].value
641        else:
642            maxsize = "0"
643
644        return _XdrString(ident.symbol, maxsize, line=ident.line, column=ident.column)
645
646    def fixed_length_array(self, children):
647        """Instantiate one _XdrFixedLengthArray declaration object"""
648        spec = children[0]
649        ident = children[1]
650        size = children[2].value
651
652        return _XdrFixedLengthArray(
653            ident.symbol, spec, size, line=ident.line, column=ident.column
654        )
655
656    def variable_length_array(self, children):
657        """Instantiate one _XdrVariableLengthArray declaration object"""
658        spec = children[0]
659        ident = children[1]
660        if children[2] is not None:
661            maxsize = children[2].value
662        else:
663            maxsize = "0"
664
665        return _XdrVariableLengthArray(
666            ident.symbol, spec, maxsize, line=ident.line, column=ident.column
667        )
668
669    def optional_data(self, children):
670        """Instantiate one _XdrOptionalData declaration object"""
671        spec = children[0]
672        ident = children[1]
673
674        return _XdrOptionalData(
675            ident.symbol, spec, line=ident.line, column=ident.column
676        )
677
678    def basic(self, children):
679        """Instantiate one _XdrBasic object"""
680        spec = children[0]
681        ident = children[1]
682
683        return _XdrBasic(ident.symbol, spec, line=ident.line, column=ident.column)
684
685    def void(self, children):
686        """Instantiate one _XdrVoid declaration object"""
687
688        return _XdrVoid()
689
690    def struct(self, children):
691        """Instantiate one _XdrStruct object"""
692        ident = children[0]
693        name = ident.symbol
694        fields = children[1].children
695        pos = {"line": ident.line, "column": ident.column}
696
697        last_field = fields[-1]
698        if (
699            isinstance(last_field, _XdrOptionalData)
700            and name == last_field.spec.type_name
701        ):
702            return _XdrPointer(name, fields, **pos)
703
704        return _XdrStruct(name, fields, **pos)
705
706    def typedef(self, children):
707        """Instantiate one _XdrTypedef object"""
708        new_type = children[0]
709
710        return _XdrTypedef(new_type)
711
712    def case_spec(self, children):
713        """Instantiate one _XdrCaseSpec object"""
714        values = []
715        for item in children[0:-1]:
716            values.append(item.value)
717        arm = children[-1]
718
719        return _XdrCaseSpec(values, arm)
720
721    def default_spec(self, children):
722        """Instantiate one _XdrDefaultSpec object"""
723        arm = children[0]
724
725        return _XdrDefaultSpec(arm)
726
727    def union(self, children):
728        """Instantiate one _XdrUnion object"""
729        ident = children[0]
730
731        body = children[1]
732        discriminant = body.children[0].children[0]
733        cases = body.children[1:-1]
734        default = body.children[-1]
735
736        return _XdrUnion(
737            ident.symbol,
738            discriminant,
739            cases,
740            default,
741            line=ident.line,
742            column=ident.column,
743        )
744
745    def procedure_def(self, children):
746        """Instantiate one _RpcProcedure object"""
747        result = children[0]
748        ident = children[1]
749        argument = children[2]
750        number = children[3].value
751
752        return _RpcProcedure(
753            ident.symbol,
754            number,
755            argument,
756            result,
757            line=ident.line,
758            column=ident.column,
759        )
760
761    def version_def(self, children):
762        """Instantiate one _RpcVersion object"""
763        ident = children[0]
764        number = children[-1].value
765        procedures = children[1:-1]
766
767        return _RpcVersion(
768            ident.symbol, number, procedures, line=ident.line, column=ident.column
769        )
770
771    def program_def(self, children):
772        """Instantiate one _RpcProgram object"""
773        ident = children[0]
774        number = children[-1].value
775        versions = children[1:-1]
776
777        return _RpcProgram(
778            ident.symbol, number, versions, line=ident.line, column=ident.column
779        )
780
781    def pragma_def(self, children):
782        """Instantiate one _Pragma object"""
783        directive = children[0].children[0].data
784        match directive:
785            case "big_endian_directive":
786                big_endian.append(children[1].symbol)
787            case "exclude_directive":
788                excluded_apis.append(children[1].symbol)
789            case "header_directive":
790                global header_name
791                header_name = children[1].symbol
792            case "public_directive":
793                public_apis.append(children[1].symbol)
794            case _:
795                raise NotImplementedError("Directive not supported")
796        return _Pragma()
797
798    def passthru_def(self, children):
799        """Instantiate one _XdrPassthru object"""
800        token = children[0]
801        content = token.value[1:]
802        return _XdrPassthru(content)
803
804
805transformer = ast_utils.create_transformer(this_module, ParseToAst())
806
807
808def _merge_consecutive_passthru(definitions: List[Definition]) -> List[Definition]:
809    """Merge consecutive passthru definitions into single nodes"""
810    result = []
811    i = 0
812    while i < len(definitions):
813        if isinstance(definitions[i].value, _XdrPassthru):
814            lines = [definitions[i].value.content]
815            meta = definitions[i].meta
816            j = i + 1
817            while j < len(definitions) and isinstance(
818                definitions[j].value, _XdrPassthru
819            ):
820                lines.append(definitions[j].value.content)
821                j += 1
822            merged = _XdrPassthru("\n".join(lines))
823            result.append(Definition(meta, merged))
824            i = j
825        else:
826            result.append(definitions[i])
827            i += 1
828    return result
829
830
831def _meta_line(meta) -> int:
832    """Return the 1-based source line for a node's meta, or 0 if unknown"""
833    try:
834        return meta.line
835    except AttributeError:
836        return 0
837
838
839class XdrSemanticError(Exception):
840    """A specification that parses but violates an XDR semantic rule.
841
842    Detection lives in the language-independent front end because a
843    duplicate name is malformed XDR regardless of the output language.
844    """
845
846    def __init__(self, message: str, meta):
847        super().__init__(message)
848        self.message = message
849        self.line = _meta_line(meta)
850        self.column = getattr(meta, "column", 0)
851
852
853def _introduced_names(value):
854    """Yield (name, node) for each identifier a definition introduces."""
855    if isinstance(value, (_XdrStruct, _XdrUnion, _XdrPointer)):
856        yield value.name, value
857    elif isinstance(value, _XdrEnum):
858        yield value.name, value
859        for enumerator in value.enumerators:
860            yield enumerator.name, enumerator
861    elif isinstance(value, _XdrTypedef):
862        yield value.declaration.name, value.declaration
863    elif isinstance(value, _XdrConstant):
864        yield value.name, value
865    elif isinstance(value, _RpcProgram):
866        yield value.name, value
867
868
869def _check_rpc_scope_names(program: "_RpcProgram") -> None:
870    """Enforce RFC 5531 Section 12.3 scoping within an RPC program.
871
872    A version name and number are unique within the program and a
873    procedure name and number are unique within its version.
874    """
875    version_names = set()
876    version_numbers = set()
877    for version in program.versions:
878        if version.name in version_names:
879            raise XdrSemanticError(
880                f"duplicate version name '{version.name}'"
881                f" in program '{program.name}'",
882                version,
883            )
884        version_names.add(version.name)
885        if version.number in version_numbers:
886            raise XdrSemanticError(
887                f"duplicate version number {version.number}"
888                f" in program '{program.name}'",
889                version,
890            )
891        version_numbers.add(version.number)
892        procedure_names = set()
893        procedure_numbers = set()
894        for procedure in version.procedures:
895            if procedure.name in procedure_names:
896                raise XdrSemanticError(
897                    f"duplicate procedure name '{procedure.name}'"
898                    f" in version '{version.name}'",
899                    procedure,
900                )
901            procedure_names.add(procedure.name)
902            if procedure.number in procedure_numbers:
903                raise XdrSemanticError(
904                    f"duplicate procedure number {procedure.number}"
905                    f" in version '{version.name}'",
906                    procedure,
907                )
908            procedure_numbers.add(procedure.number)
909
910
911def check_duplicate_definitions(root: "Specification") -> None:
912    """Reject a spec that declares an identifier more than once.
913
914    RFC 4506 Section 6.4 places constant and type identifiers in a
915    single name space that must be unique within a specification.
916    RFC 5531 Section 12.3 adds RPC program names to that name space
917    and scopes version names and numbers to their program and
918    procedure names and numbers to their version.
919    """
920    seen = {}
921    for definition in root.definitions:
922        for name, node in _introduced_names(definition.value):
923            where = node if node.line else definition.meta
924            first = seen.get(name)
925            if first is not None:
926                raise XdrSemanticError(
927                    f"duplicate identifier '{name}'"
928                    f" (first declared at line {_meta_line(first)})",
929                    where,
930                )
931            seen[name] = where
932        if isinstance(definition.value, _RpcProgram):
933            _check_rpc_scope_names(definition.value)
934
935
936# RFC 5531 (Section 9) encodes program, version, and procedure numbers
937# as unsigned 32-bit integers, so each must fall within [0, 2**32 - 1].
938_RPC_NUMBER_MAX = 2**32 - 1
939
940
941def _check_rpc_number(kind: str, number: int, scope: str, meta) -> None:
942    """Reject one RPC number that is negative or wider than 32 bits."""
943    if number < 0:
944        raise XdrSemanticError(
945            f"negative {kind} number {number} {scope}",
946            meta,
947        )
948    if number > _RPC_NUMBER_MAX:
949        raise XdrSemanticError(
950            f"{kind} number {number} {scope} exceeds {_RPC_NUMBER_MAX}",
951            meta,
952        )
953
954
955def check_rpc_number_range(root: "Specification") -> None:
956    """Reject an out-of-range program, version, or procedure number.
957
958    RFC 5531 assigns only unsigned constants to program, version, and
959    procedure numbers (Section 12.3) and encodes each as an unsigned
960    32-bit integer (Section 9). RFC 4506 Section 6.2 permits a signed
961    decimal constant for XDR constants in general and sets no ceiling on
962    magnitude, so the grammar accepts an out-of-range value; the range
963    is enforced here instead. The parser retains no per-version or
964    per-procedure source location, so a violation is reported against the
965    program definition.
966    """
967    for definition in root.definitions:
968        program = definition.value
969        if not isinstance(program, _RpcProgram):
970            continue
971        _check_rpc_number(
972            "program",
973            program.number,
974            f"in program '{program.name}'",
975            definition.meta,
976        )
977        for version in program.versions:
978            _check_rpc_number(
979                "version",
980                version.number,
981                f"in program '{program.name}'",
982                definition.meta,
983            )
984            for procedure in version.procedures:
985                _check_rpc_number(
986                    "procedure",
987                    procedure.number,
988                    f"in version '{version.name}'",
989                    definition.meta,
990                )
991
992
993def transform_parse_tree(parse_tree):
994    """Transform productions into an abstract syntax tree"""
995    ast = transformer.transform(parse_tree)
996    ast.definitions = _merge_consecutive_passthru(ast.definitions)
997    check_duplicate_definitions(ast)
998    check_rpc_number_range(ast)
999    return ast
1000
1001
1002def get_header_name() -> str:
1003    """Return header name set by pragma header directive"""
1004    return header_name
1005