1#!/usr/bin/env python3
2# ex: set filetype=python:
3
4"""Translate an XDR specification into executable code that
5can be compiled for the Linux kernel."""
6
7import logging
8
9from argparse import Namespace
10from lark import logger
11from lark.exceptions import UnexpectedInput
12
13from generators.source_top import XdrSourceTopGenerator
14from generators.enum import XdrEnumGenerator
15from generators.pointer import XdrPointerGenerator
16from generators.program import XdrProgramGenerator
17from generators.typedef import XdrTypedefGenerator
18from generators.struct import XdrStructGenerator
19from generators.union import XdrUnionGenerator
20
21from xdr_ast import transform_parse_tree, _RpcProgram, Specification
22from xdr_ast import _XdrAst, _XdrEnum, _XdrPointer
23from xdr_ast import _XdrStruct, _XdrTypedef, _XdrUnion
24
25from xdr_parse import xdr_parser, set_xdr_annotate
26
27logger.setLevel(logging.INFO)
28
29
30def emit_source_decoder(node: _XdrAst, language: str, peer: str) -> None:
31    """Emit one XDR decoder function for a source file"""
32    if isinstance(node, _XdrEnum):
33        gen = XdrEnumGenerator(language, peer)
34    elif isinstance(node, _XdrPointer):
35        gen = XdrPointerGenerator(language, peer)
36    elif isinstance(node, _XdrTypedef):
37        gen = XdrTypedefGenerator(language, peer)
38    elif isinstance(node, _XdrStruct):
39        gen = XdrStructGenerator(language, peer)
40    elif isinstance(node, _XdrUnion):
41        gen = XdrUnionGenerator(language, peer)
42    elif isinstance(node, _RpcProgram):
43        gen = XdrProgramGenerator(language, peer)
44    else:
45        return
46    gen.emit_decoder(node)
47
48
49def emit_source_encoder(node: _XdrAst, language: str, peer: str) -> None:
50    """Emit one XDR encoder function for a source file"""
51    if isinstance(node, _XdrEnum):
52        gen = XdrEnumGenerator(language, peer)
53    elif isinstance(node, _XdrPointer):
54        gen = XdrPointerGenerator(language, peer)
55    elif isinstance(node, _XdrTypedef):
56        gen = XdrTypedefGenerator(language, peer)
57    elif isinstance(node, _XdrStruct):
58        gen = XdrStructGenerator(language, peer)
59    elif isinstance(node, _XdrUnion):
60        gen = XdrUnionGenerator(language, peer)
61    elif isinstance(node, _RpcProgram):
62        gen = XdrProgramGenerator(language, peer)
63    else:
64        return
65    gen.emit_encoder(node)
66
67
68def generate_server_source(filename: str, root: Specification, language: str) -> None:
69    """Generate server-side source code"""
70
71    gen = XdrSourceTopGenerator(language, "server")
72    gen.emit_source(filename, root)
73
74    for definition in root.definitions:
75        emit_source_decoder(definition.value, language, "server")
76    for definition in root.definitions:
77        emit_source_encoder(definition.value, language, "server")
78
79
80def generate_client_source(filename: str, root: Specification, language: str) -> None:
81    """Generate server-side source code"""
82
83    gen = XdrSourceTopGenerator(language, "client")
84    gen.emit_source(filename, root)
85
86    # cel: todo: client needs XDR size macros
87
88    for definition in root.definitions:
89        emit_source_encoder(definition.value, language, "client")
90    for definition in root.definitions:
91        emit_source_decoder(definition.value, language, "client")
92
93    # cel: todo: client needs PROC macros
94
95
96def handle_parse_error(e: UnexpectedInput) -> bool:
97    """Simple parse error reporting, no recovery attempted"""
98    print(e)
99    return True
100
101
102def subcmd(args: Namespace) -> int:
103    """Generate encoder and decoder functions"""
104
105    set_xdr_annotate(args.annotate)
106    parser = xdr_parser()
107    with open(args.filename, encoding="utf-8") as f:
108        parse_tree = parser.parse(f.read(), on_error=handle_parse_error)
109        ast = transform_parse_tree(parse_tree)
110        match args.peer:
111            case "server":
112                generate_server_source(args.filename, ast, args.language)
113            case "client":
114                generate_client_source(args.filename, ast, args.language)
115            case _:
116                print("Code generation for", args.peer, "is not yet supported")
117
118    return 0
119