summaryrefslogtreecommitdiff
path: root/lib9p/protogen/c_unmarshal.py
blob: ea484b0c77559e845afeeba461eaf4f1f7c228e0 (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
# lib9p/protogen/c_unmarshal.py - Generate C unmarshal functions
#
# Copyright (C) 2024-2025  Luke T. Shumaker <lukeshu@lukeshu.com>
# SPDX-License-Identifier: AGPL-3.0-or-later

import typing

import idl

from . import c9util, cutil, idlutil

# 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_unmarshal"]


def gen_c_unmarshal(versions: set[str], typs: list[idl.UserType]) -> str:
    ret = """
/* unmarshal_* ****************************************************************/

"""
    ret += cutil.macro(
        "#define UNMARSHAL_BYTES(ctx, data_lvalue, len)\n"
        "\tdata_lvalue = (char *)&ctx->net_bytes[ctx->net_offset];\n"
        "\tctx->net_offset += len;\n"
    )
    ret += cutil.macro(
        "#define UNMARSHAL_U8LE(ctx, val_lvalue)\n"
        "\tval_lvalue = ctx->net_bytes[ctx->net_offset];\n"
        "\tctx->net_offset += 1;\n"
    )
    ret += cutil.macro(
        "#define UNMARSHAL_U16LE(ctx, val_lvalue)\n"
        "\tval_lvalue = uint16le_decode(&ctx->net_bytes[ctx->net_offset]);\n"
        "\tctx->net_offset += 2;\n"
    )
    ret += cutil.macro(
        "#define UNMARSHAL_U32LE(ctx, val_lvalue)\n"
        "\tval_lvalue = uint32le_decode(&ctx->net_bytes[ctx->net_offset]);\n"
        "\tctx->net_offset += 4;\n"
    )
    ret += cutil.macro(
        "#define UNMARSHAL_U64LE(ctx, val_lvalue)\n"
        "\tval_lvalue = uint64le_decode(&ctx->net_bytes[ctx->net_offset]);\n"
        "\tctx->net_offset += 8;\n"
    )

    class IndentLevel(typing.NamedTuple):
        ifdef: bool  # whether this is both `{` and `#if`, or just `{`

    indent_stack: list[IndentLevel]

    def ifdef_lvl() -> int:
        return sum(1 if lvl.ifdef else 0 for lvl in indent_stack)

    def indent_lvl() -> int:
        return len(indent_stack)

    def handle(
        path: idlutil.Path,
    ) -> tuple[idlutil.WalkCmd, typing.Callable[[], None]]:
        nonlocal ret
        nonlocal indent_stack
        indent_stack_len = len(indent_stack)

        def pop() -> None:
            nonlocal ret
            nonlocal indent_stack
            nonlocal indent_stack_len
            while len(indent_stack) > indent_stack_len:
                if len(indent_stack) == indent_stack_len + 1 and indent_stack[-1].ifdef:
                    break
                ret += f"{'\t'*(indent_lvl()-1)}}}\n"
                if indent_stack.pop().ifdef:
                    ret += cutil.ifdef_pop(ifdef_lvl())

        if not path.elems:
            return idlutil.WalkCmd.KEEP_GOING, pop

        child = path.elems[-1]
        parent = path.elems[-2].typ if len(path.elems) > 1 else path.root
        if child.in_versions < parent.in_versions:
            if line := cutil.ifdef_push(
                ifdef_lvl() + 1, c9util.ver_ifdef(child.in_versions)
            ):
                ret += line
                ret += (
                    f"{'\t'*indent_lvl()}if ({c9util.ver_cond(child.in_versions)}) {{\n"
                )
                indent_stack.append(IndentLevel(ifdef=True))
        if child.cnt:
            cnt_path = path.parent().add(child.cnt)
            if child.typ.static_size == 1:  # SPECIAL (zerocopy)
                ret += f"{'\t'*indent_lvl()}UNMARSHAL_BYTES(ctx, {path.c_str('out->')[:-3]}, {cnt_path.c_str('out->')});\n"
                return idlutil.WalkCmd.KEEP_GOING, pop
            ret += f"{'\t'*indent_lvl()}{path.c_str('out->')[:-3]} = ctx->extra;\n"
            ret += f"{'\t'*indent_lvl()}ctx->extra += sizeof({path.c_str('out->')[:-3]}[0]) * {cnt_path.c_str('out->')};\n"
            loopdepth = sum(1 for elem in path.elems if elem.cnt)
            loopvar = chr(ord("i") + loopdepth - 1)
            ret += f"{'\t'*indent_lvl()}for ({c9util.typename(child.cnt.typ)} {loopvar} = 0; {loopvar} < {cnt_path.c_str('out->')}; {loopvar}++) {{\n"
            indent_stack.append(IndentLevel(ifdef=False))
        if not isinstance(child.typ, idl.Struct):
            if child.val:
                ret += (
                    f"{'\t'*indent_lvl()}ctx->net_offset += {child.typ.static_size};\n"
                )
            else:
                ret += f"{'\t'*indent_lvl()}UNMARSHAL_U{child.typ.static_size*8}LE(ctx, {path.c_str('out->')});\n"
        return idlutil.WalkCmd.KEEP_GOING, pop

    for typ in typs:
        if not (
            isinstance(typ, idl.Message) or typ.typname == "stat"
        ):  # SPECIAL (include stat)
            continue
        assert isinstance(typ, idl.Struct)
        ret += "\n"
        ret += cutil.ifdef_push(1, c9util.ver_ifdef(typ.in_versions))
        ret += f"static void unmarshal_{typ.typname}(struct _unmarshal_ctx *ctx, {c9util.typename(typ)} *out) {{\n"

        indent_stack = [IndentLevel(ifdef=True)]
        idlutil.walk(typ, handle)
        while len(indent_stack) > 0:
            ret += f"{'\t'*(indent_lvl()-1)}}}\n"
            if indent_stack.pop().ifdef and indent_stack:
                ret += cutil.ifdef_pop(ifdef_lvl())
    ret += cutil.ifdef_pop(0)
    return ret