summaryrefslogtreecommitdiff
path: root/lib9p/protogen/c_format.py
blob: a1bcbf3fa1d7563270a14899e1d6749bae40d7f6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# lib9p/protogen/c_format.py - Generate C pretty-print functions
#
# Copyright (C) 2024-2025  Luke T. Shumaker <lukeshu@lukeshu.com>
# SPDX-License-Identifier: AGPL-3.0-or-later


import idl

from . import c9util, cutil

# This strives to be "general-purpose" in that it just acts on the
# *.9p inputs; but (unfortunately?) there are a few special-cases in
# this script, marked with "SPECIAL".

# pylint: disable=unused-variable
__all__ = ["gen_c_format"]


def bf_numname(typ: idl.Bitfield, num: idl.BitNum, base: str) -> str:
    prefix = f"{typ.typname}_{num.numname}_".upper()
    return c9util.Ident(c9util.add_prefix(prefix, base))


def gen_c_format(versions: set[str], typs: list[idl.UserType]) -> str:
    ret = """
/* *_format *******************************************************************/
"""
    for typ in typs:
        ret += "\n"
        ret += cutil.ifdef_push(1, c9util.ver_ifdef(typ.in_versions))
        ret += f"static void {c9util.basename(typ)}_format({c9util.typename(typ)} *self, struct fmt_state *state) {{\n"
        match typ:
            case idl.Number():
                if typ.vals:
                    ret += "\tswitch (*self) {\n"
                    for name in typ.vals:
                        ret += f"\tcase {c9util.Ident(c9util.add_prefix(f'{typ.typname}_'.upper(), name))}:\n"
                        ret += f'\t\tfmt_state_puts(state, "{name}");\n'
                        ret += "\t\tbreak;\n"
                    ret += "\tdefault:\n"
                    ret += f'\t\tfmt_state_printf(state, "%"PRIu{typ.static_size*8}, *self);\n'
                    ret += "\t}\n"
                else:
                    ret += f'\t\tfmt_state_printf(state, "%"PRIu{typ.static_size*8}, *self);\n'
            case idl.Bitfield():
                val = "*self"
                if typ.typname == "dm":  # SPECIAL (pretty file permissions)
                    val = f"(*self & ~(({c9util.typename(typ)})0777))"
                ret += "\tbool empty = true;\n"
                ret += "\tfmt_state_putchar(state, '(');\n"
                nums: set[str] = set()

                for bit in reversed(typ.bits):
                    match bit.cat:
                        case "UNUSED" | "USED" | "RESERVED":
                            if bit.cat == "UNUSED":
                                bitname = f"1<<{bit.num}"
                            else:
                                bitname = bit.bitname
                            ret += f"\tif ({val} & (UINT{typ.static_size*8}_C(1)<<{bit.num})) {{\n"
                            ret += "\t\tif (!empty)\n"
                            ret += "\t\t\tfmt_state_putchar(state, '|');\n"
                            ret += f'\t\tfmt_state_puts(state, "{bitname}");\n'
                            ret += "\t\tempty = false;\n"
                            ret += "\t}\n"
                        case idl.BitNum():
                            if bit.cat.numname in nums:
                                continue
                            ret += f"\tswitch ({val} & {bf_numname(typ, bit.cat, 'MASK')}) {{\n"
                            for name in bit.cat.vals:
                                ret += f"\tcase {bf_numname(typ, bit.cat, name)}:\n"
                                bitname = c9util.add_prefix(
                                    f"{bit.cat.numname}_".upper(), name
                                )
                                ret += "\t\tif (!empty)\n"
                                ret += "\t\t\tfmt_state_putchar(state, '|');\n"
                                ret += f'\t\tfmt_state_puts(state, "{bitname}");\n'
                                ret += "\t\tempty = false;\n"
                                ret += "\t\tbreak;\n"
                            ret += "\tdefault:\n"
                            ret += "\t\tif (!empty)\n"
                            ret += "\t\t\tfmt_state_putchar(state, '|');\n"
                            ret += f'\t\tfmt_state_printf(state, "%"PRIu{typ.static_size*8}, {val} & {bf_numname(typ, bit.cat, 'MASK')});\n'
                            ret += "\t\tempty = false;\n"
                            ret += "\t}\n"
                            nums.add(bit.cat.numname)
                if typ.typname == "dm":  # SPECIAL (pretty file permissions)
                    ret += "\tif (!empty)\n"
                    ret += "\t\tfmt_state_putchar(state, '|');\n"
                    ret += f'\tfmt_state_printf(state, "%#04"PRIo{typ.static_size*8}, *self & 0777);\n'
                else:
                    ret += "\tif (empty)\n"
                    ret += "\t\tfmt_state_putchar(state, '0');\n"
                ret += "\tfmt_state_putchar(state, ')');\n"
            case idl.Struct(typname="s"):  # SPECIAL(string)
                ret += "\t/* https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47781 */\n"
                ret += "#pragma GCC diagnostic push\n"
                ret += '#pragma GCC diagnostic ignored "-Wformat"\n'
                ret += '#pragma GCC diagnostic ignored "-Wformat-extra-args"\n'
                ret += '\tfmt_state_printf(state, "%.*q", self->len, self->utf8);\n'
                ret += "#pragma GCC diagnostic pop\n"
            case idl.Struct():  # and idl.Message():
                if isinstance(typ, idl.Message):
                    ret += f'\tfmt_state_puts(state, "{typ.typname} {{");\n'
                else:
                    ret += "\tfmt_state_putchar(state, '{');\n"
                for member in typ.members:
                    if member.val:
                        continue
                    ret += cutil.ifdef_push(2, c9util.ver_ifdef(member.in_versions))
                    if member.cnt:
                        if member.typ.static_size == 1:  # SPECIAL (data)
                            ret += f'\tfmt_state_puts(state, " {member.membname}=<bytedata>");\n'
                            continue
                        if isinstance(member.cnt, int):
                            cnt_str = str(member.cnt)
                            cnt_typ = "size_t"
                        else:
                            cnt_str = f"self->{member.cnt.membname}"
                            cnt_typ = c9util.typename(member.cnt.typ)
                        ret += f'\tfmt_state_puts(state, " {member.membname}=[");\n'
                        ret += f"\tfor ({cnt_typ} i = 0; i < {cnt_str}; i++) {{\n"
                        ret += "\t\tif (i)\n"
                        ret += '\t\t\tfmt_state_puts(state, ", ");\n'
                        if isinstance(member.typ, idl.Primitive):
                            ret += f'\t\tfmt_state_printf(state, "%"PRIu{member.typ.static_size*8}, self->{member.membname}[i]);\n'
                        else:
                            ret += f"\t\t{c9util.basename(member.typ)}_format(&self->{member.membname}[i], state);\n"
                        ret += "\t}\n"
                        ret += '\tfmt_state_puts(state, " ]");\n'
                    else:
                        ret += f'\tfmt_state_puts(state, " {member.membname}=");\n'
                        if isinstance(member.typ, idl.Primitive):
                            ret += f'\tfmt_state_printf(state, "%"PRIu{member.typ.static_size*8}, self->{member.membname});\n'
                        else:
                            ret += f"\t{c9util.basename(member.typ)}_format(&self->{member.membname}, state);\n"
                ret += cutil.ifdef_pop(1)
                ret += '\tfmt_state_puts(state, " }");\n'
        ret += "}\n"
    ret += cutil.ifdef_pop(0)

    return ret