summaryrefslogtreecommitdiff
path: root/lib9p/protogen/c_unmarshal.py
blob: d076352cd659b7c6650ff2e212332cd7f0fa0387 (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
# 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:
                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:
            ret += cutil.ifdef_push(
                ifdef_lvl() + 1, c9util.ver_ifdef(child.in_versions)
            )
            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)

        ret += "}\n"
    ret += cutil.ifdef_pop(0)
    return ret