Source code for zoti_ftn.backend.c

#!/usr/bin/env python3

from typing import Dict, Union, Tuple, Optional, List, Set
from copy import deepcopy

import zoti_ftn.core as ftn
import zoti_ftn.tokens as tok
from zoti_ftn.exceptions import FtnError
from zoti_ftn.util import with_schema, post_load

INT_ATTRS_TABLE = [
    (0, 0xFF, (True, 8)),
    (-0x80, 0x7F, (False, 8)),
    (0, 0xFFFF, (True, 16)),
    (-0x8000, 0x7FFF, (False, 16)),
    (0, 0xFFFFFFFF, (True, 32)),
    (-0x80000000, 0x7FFFFFFF, (False, 32)),
    (0, 0xFFFFFFFFFFFFFFFF, (True, 64)),
    (-0x8000000000000000, 0x7FFFFFFFFFFFFFFF, (False, 64)),
]

INT_ATTRS_TO_STR = {
    (True, 8): "uint8_t",
    (False, 8): "int8_t",
    (True, 16): "uint16_t",
    (False, 16): "int16_t",
    (True, 32): "uint32_t",
    (False, 32): "int32_t",
    (True, 64): "uint64_t",
    (False, 64): "int64_t",
}

# Key is a tuple (reverse, unsigned, size). Result is tuple (prefix, suffix).
INT_SWAP_FUNCS = {
    (False, False, 8): ("", ""),
    (False, True, 8): ("", ""),
    (True, False, 8): ("", ""),
    (True, True, 8): ("", ""),
    (False, False, 16): ("", ""),
    (False, True, 16): ("", ""),
    (True, False, 16): ("DFL_swap_int16(", ")"),
    (True, True, 16): ("DFL_swap_uint16(", ")"),
    (False, False, 32): ("", ""),
    (False, True, 32): ("", ""),
    (True, False, 32): ("DFL_swap_int32(", ")"),
    (True, True, 32): ("DFL_swap_uint32(", ")"),
    (False, False, 64): ("", ""),
    (False, True, 64): ("", ""),
    (True, False, 64): ("DFL_swap_int64(", ")"),
    (True, True, 64): ("DFL_swap_uint64(", ")"),
}

TARGET_ENDIAN = "little"


def _mangle_to_C_name(uid: ftn.Uid):
    mangled = f"{uid.module}__{uid.name}"
    return mangled.replace("^", "_A_").replace("-", "_")


class TypeABC(ftn.TypeABC):
    def has_subscript(self) -> bool:
        """Returns false this type does not need indexing."""
        return False

    def need_malloc(self) -> bool:
        """Returns false. This type does not need memory allocation."""
        return False

    def gen_length_expr(self, arr_expr: str) -> Tuple[Optional[str], Optional[bool]]:
        """Returns a tuple of None. This type does not have a length
        expression."""
        return (None, None)

    def gen_ctor(self, buf_var: str, acc_expr: str, ptr_fixup: bool = True) -> List[str]:
        """If the type needs memory allocation (see :meth:`need_malloc`) it
        generates the statements for a constructor and returns
        it. Otherwise it returns an empty list.

        :arg buf_var: name of the buffer variable.

        :arg acc_expr: access expression for data in *buf_var* (e.g.,
          pointer notation)

        :arg prt_fixup: true if it needs a pointer fixup expression
          after construction (i.e., update the pointer).

        """
        if not self.need_malloc():
            return []

        base_size_expr = self._gen_base_size_expr(acc_expr)
        arr_infos = self._gen_arr_infos(acc_expr)
        size_exprs = [base_size_expr] + [sz for _, sz, _ in arr_infos]
        # print(base_size_expr, size_exprs, arr_infos)
        ctor_lines = [f"{buf_var} = malloc({' + '.join(size_exprs)})"]
        if ptr_fixup:
            last_ptr = buf_var
            last_size = base_size_expr
            for arr_ptr, sz, elem_c_type in arr_infos:
                assign = (
                    f"{arr_ptr} = ({elem_c_type} *)((char *)({last_ptr}) + {last_size})"
                )
                ctor_lines.append(assign)
                last_ptr = arr_ptr
                last_size = sz
        return ctor_lines

    def gen_desctor(self, buf_var: str, buf_val: Optional[str]=None) -> List[str]:
        """If the type needs memory allocation (see :meth:`need_malloc`) it
        generates the statements for a destructor and returns
        it. Otherwise it returns an empty list.

        :arg buf_var: name of the buffer variable
        :arg buf_val: initial (reset) value for the buffer.

        """
        if not self.need_malloc():
            return []
        dctor = [f"free({buf_var})"]
        if buf_val is not None:
            dctor.append(f"{buf_var} = {buf_val}")
        return dctor

    def gen_copy(self, dst_ptr: str, dst_acc_expr: str, src_ptr: str, src_acc_expr: str
                 ) -> List[str]:
        """Generates the statements for a copy expressions.

        :arg dst_ptr: name of the destination buffer.

        :arg dst_acc_expr: access expression for the data in *dst_ptr* 

        :arg src_ptr: name of the source buffer.

        :arg src_acc_expr: access expression for the data in *src_ptr* 

        """
        base_size_expr = self._gen_base_size_expr(dst_acc_expr)
        dst_arr_infos = self._gen_arr_infos(dst_acc_expr)
        src_arr_infos = self._gen_arr_infos(src_acc_expr)
        size_exprs = [base_size_expr] + [sz for _, sz, _ in dst_arr_infos]
        tot_size = "(" + " + ".join(size_exprs) + ")"
        stmt = [f"memcpy({dst_ptr}, {src_ptr}, {tot_size})"]
        for dst_info, src_info in zip(dst_arr_infos, src_arr_infos):
            dst_arr_ptr, _, elem_c_type = dst_info
            src_arr_ptr, _, _ = src_info
            cpy = (
                f"{dst_arr_ptr} = ({elem_c_type}*)((char*)({dst_ptr})"
                f" + ((char*)({src_arr_ptr}) - (char*)({src_ptr})))"
            )
            stmt.append(cpy)
        return stmt

    def gen_size_expr(self, acc_expr: str) -> str:
        """Generates an expression that calculates the total size of the data
        contained by this type.

        :arg acc_expr: data access expression. 
        """
        base_size_expr = self._gen_base_size_expr(acc_expr)
        arr_infos = self._gen_arr_infos(acc_expr)
        size_exprs = [base_size_expr] + [sz for _, sz, _ in arr_infos]
        return "(" + " + ".join(size_exprs) + ")"

    # TODO: Making _gen_base_size_expr public as below is just a temporary hack
    #   to get marshalling working again. The type handling needs rethinking.
    def gen_base_size_expr(self, acc_expr: str) -> str:
        """Generates an expression that calculates the header size for this
        type (i.e., the first element)

        :arg acc_expr: data access expression.

        """
        return self._gen_base_size_expr(acc_expr)

    def gen_marshal(self, buf_ptr: str, acc_expr: str) -> List[str]:
        """Generates the statements for type marshalling.

        :arg buf_ptr: name of the destination buffer.

        :arg acc_expr: access expression for the data at *buf_ptr*

        """
        return self._gen_marshalling(buf_ptr, acc_expr, 0)

    def gen_unmarshal(self, buf_ptr: str, acc_expr: str) -> List[str]:
        """Generates the statements for type marshalling.

        :arg buf_ptr: name of the destination buffer.

        :arg acc_expr: access expression for the data at *buf_ptr*

        """
        return self._gen_marshalling(buf_ptr, acc_expr, 0, inverse=True)

    def requirements(self) -> Set[str]:
        """This type has no include requirement."""
        return []

    def _gen_arr_infos(self, acc_expr):
        return []


#@with_schema(ftn.TypeRef.Schema)
[docs]class TypeRef(ftn.TypeRef, TypeABC): """Qualified reference to another (defined) type."""
[docs] def requirements(self) -> Set[str]: """Returns the include requirements of the referred type""" return self.get_referred_type().requirements()
[docs] def has_subscript(self) -> bool: """Checks if referred type needs to be indexed.""" return self.get_referred_type().has_subscript()
[docs] def gen_ctype_expr(self, name, allow_void=False) -> str: """Returns the name of the referred type. :return: (prefix, "") """ ref = self.ref return (_mangle_to_C_name(ref) + "_t", "")
[docs] def gen_access_expr(self, src_expr: str , iter_lvl: int ) -> List[Tuple[List, List, str, str]]: """Returns the components to build the access expression for the referred type. :arg src_expr: arbitrary name for the variable in the access macro. :arg iter_lvl: how deep the current index level is. :return: list of 4-tuples (field-path, access-args, access-expr, read-only) """ return self.get_referred_type().gen_access_expr(src_expr, iter_lvl)
[docs] def gen_subscript_expr(self, arr_expr: str) -> Tuple[str, str]: """Returns the components for generating an eventual subscript expression for the referred type. :arg arr_expr: expression which evauates to an array type. """ return self.get_referred_type().gen_subscript_expr(arr_expr)
[docs] def gen_length_expr(self, arr_expr: str) -> Tuple[str, bool]: """Returns the components to build the length expression for the referred type :arg arr_expr: expression which evauates to an array type. """ return self.get_referred_type().gen_length_expr(arr_expr)
[docs] def need_malloc(self) -> bool: """Checks if the referred type needs memory allocation.""" return self.get_referred_type().need_malloc()
def _gen_marshalling(self, buf_ptr, acc_expr, iter_lvl, inverse=False): return self.get_referred_type()._gen_marshalling( buf_ptr, acc_expr, iter_lvl, inverse ) def _gen_base_size_expr(self, acc_expr): c_type, _ = self.gen_ctype_expr(None) return "sizeof({})".format(c_type) def _gen_arr_infos(self, acc_expr): return self.get_referred_type()._gen_arr_infos(acc_expr) class Schema(ftn.TypeRef.Schema): @post_load def construct(self, data, **kwargs): return TypeRef(**data)
[docs]class Structure(ftn.Structure, TypeABC): """A structure type is represented as a dictionary of other types."""
[docs] def requirements(self) -> Set[str]: """Returns the set of include requirements for all fields.""" return set().union(*[f.requirements() for f in self.field.values()])
[docs] def gen_ctype_expr(self, name: str, allow_void: bool = False) -> Tuple[str, str]: """Generates the full C expression for defining this struct type. Structure definitions do not have a suffix, hence the second element of the tupe is an empty string. :return: (prefix, "") """ spc = " " if name else "" type_str = f"struct{spc}{name} {{\n" for n, t in self.field.items(): prefix, suffix = t.gen_ctype_expr("", allow_void=allow_void) prefix = " ".join([ln for ln in prefix.splitlines(keepends=True)]) type_str += f" {prefix} {n}{suffix};\n" type_str += "}" return (type_str, "")
[docs] def gen_access_expr(self, src_expr: str , iter_lvl: int ) -> List[Tuple[List, List, str, str]]: """Returns the components to build the access expression every field in this structure. :arg src_expr: arbitrary name for the variable in the access macro. :arg iter_lvl: how deep the current index level is. :return: list of 4-tuples (field-path, access-args, access-expr, read-only) """ new_exprs = [] for k, v in self.field.items(): sub_exprs = v.gen_access_expr(f"{src_expr}.{k}", iter_lvl) ext_names = [([k] + n, a, e, ro) for n, a, e, ro in sub_exprs] new_exprs.extend(ext_names) return new_exprs
[docs] def need_malloc(self) -> bool: """Checks if any of the fields needs memory allocation""" for v in self.field.values(): if v.need_malloc(): return True return False
def _gen_marshalling(self, buf_ptr, acc_expr, iter_lvl, inverse=False): new_stmts = [] for k, v in self.field.items(): sub_stmts = v._gen_marshalling( buf_ptr, f"{acc_expr}.{k}", iter_lvl, inverse ) new_stmts.extend(sub_stmts) return new_stmts def _gen_base_size_expr(self, acc_expr): return "sizeof({})".format(acc_expr) def _gen_arr_infos(self, acc_expr): size_exprs = [] for k, v in self.field.items(): size_exprs.extend(v._gen_arr_infos(f"{acc_expr}.{k}")) return size_exprs class Schema(ftn.Structure.Schema): @post_load def construct(self, data, **kwargs): return Structure(**data)
[docs]class Array(ftn.Array, TypeABC): """Array type."""
[docs] def requirements(self) -> Set[str]: """Returns the requirements of the base type.""" return self.element_type.requirements()
[docs] def has_subscript(self) -> bool: """Returns true. An array needs indexing.""" return True
[docs] def gen_ctype_expr(self, name, allow_void=False) -> Tuple[str, str]: """Generates the C expression defining this array. If its range is fixed (i.e., *range.low* = *range.high*) then it is defined as a statically-allocated C array. Otherwise, it is defined as a structure with inferred elements such as pointer, offset and length. :return: (prefix, suffix) """ # TODO: This blindly does a memory layout to work with 64-bit machines # and that works with 32-bits as well. It does not take into account # byte ordering. It should all be handled in a different way, and # probably be a transformation at the model level (e.g. FTN or JSFTN) # so that the code gen is straight-forward. range_low, range_high = self.range.values() elem_type = self.element_type prefix, suffix = elem_type.gen_ctype_expr(name, allow_void=allow_void) if range_low == range_high: return (prefix, f"{suffix}[{range_high}]") elif self.len_field is not None: new_prefix = ( f"union {{\n" f" {prefix} *arr{suffix};\n" f" uint64_t offset;\n" f"}}" ) return (new_prefix, "") else: len_type_strs = [ INT_ATTRS_TO_STR[attrs] for attrs_low, attrs_high, attrs in INT_ATTRS_TABLE if range_low >= attrs_low and range_high <= attrs_high ] if not len(len_type_strs) > 0: msg = "could not deduce array length" raise FtnError(msg, **vars(self)) new_prefix = ( f"struct {{\n" f" union {{\n" f" {prefix} *arr{suffix};\n" f" uint64_t offset;\n" f" }};\n" f" {len_type_strs[0]} len;\n" f"}}" ) return (new_prefix, "")
[docs] def gen_access_expr(self, src_expr: str , iter_lvl: int ) -> List[Tuple[List, List, str, str]]: """Returns the components to build the access expression for everything contained in the element with an appended static LEN field. :arg src_expr: arbitrary name for the variable in the access macro. :arg iter_lvl: how deep the current index level is. :return: list of 4-tuples (field-path, access-args, access-expr, read-only) """ # print(vars(self)) iter_lvl += 1 iter_var = f"i{iter_lvl}" # len_field = f"{src_expr}.len" if self.len_field is None else str( # self.len_field) if self.range.low == self.range.high: len_field = f"{self.range.low}" len_readonly = True new_src_expr = f"{src_expr}[{iter_var}]" else: len_field = f"{src_expr}.len" len_readonly = False new_src_expr = f"{src_expr}.arr[{iter_var}]" # print(len_field, len_readonly) sub_exprs = self.element_type.gen_access_expr(new_src_expr, iter_lvl) new_exprs = [(n, [iter_var] + a, e, ro) for n, a, e, ro in sub_exprs] new_exprs.append((["LEN"], [], len_field, len_readonly)) return new_exprs
[docs] def gen_subscript_expr(self, arr_expr: str) -> Tuple[str, str]: """Generates an indexing expression. :return: ("<expr>[", "]") """ rlow, rhigh = self.range.values() prefix = ( f"{arr_expr}[" if self.range.low == self.range.high else f"{arr_expr}.arr[" ) suffix = "]" return prefix, suffix
[docs] def gen_length_expr(self, arr_expr: str) -> Tuple[str, bool]: """Generates a lenghth expression, together with a bool representing whether the length is fixed or not.""" if self.range.low == self.range.high: return (str(self.range.high), True) if self.len_field is not None: assert False, "TODO: This needs to refer to the surrounding Structure." return (f"{arr_expr}.len", False)
[docs] def need_malloc(self) -> bool: """Returns true if the length is not fixed or if the element needs memory allocation; otherwise false. """ if self.range.low != self.range.high: return True return self.element_type.need_malloc()
def _gen_marshalling(self, buf_ptr, acc_expr, iter_lvl, inverse=False): iter_lvl += 1 iter_var = f"i{iter_lvl}" stmts = [] if self.range.low == self.range.high: new_acc = f"{acc_expr}[{iter_var}]" else: if not iter_lvl < 2: msg = "Cannot currently handle nested dynamic arrays." raise FtnError(msg, **vars(self)) new_acc = "{acc_expr}.arr[{iter_var}]" prefix, suffix = self.element_type.gen_ctype_expr("") elem_type = prefix + suffix if inverse: stmts.append( f"{acc_expr}.arr = ({elem_type}*)((char*){buf_ptr} + {acc_expr}.offset)" ) else: stmts.append( f"{acc_expr}.offset = (char*)({acc_expr}.arr) - (char*)({buf_ptr})" ) sub_stmts = self.element_type._gen_marshalling( new_acc, iter_lvl, inverse) stmts.extend(sub_stmts) return stmts def _gen_base_size_expr(self, acc_expr): return f"sizeof({acc_expr})" def _gen_arr_infos(self, acc_expr): # TODO: This only handles the case when the array entry is a pointer and # the actual array needs to be malloced. The case when the array is of # static size should also be handled. sub_acc = f"{acc_expr}[0]" elem_exprs = [self.element_type._gen_base_size_expr(sub_acc)] elem_exprs.extend(self.element_type._gen_arr_infos(sub_acc)) et_prefix, et_suffix = self.element_type.gen_ctype_expr("") element_c_type = et_prefix + et_suffix return [ ( f"{acc_expr}.arr", f"{self.range.high} * ({'+'.join(elem_exprs)})", element_c_type, ) ] class Schema(ftn.Array.Schema): @post_load def construct(self, data, **kwargs): return Array(**data)
[docs]class Void(ftn.Void, TypeABC):
[docs] def gen_ctype_expr(self, name, allow_void=False) -> Tuple[str, str]: """If *allow_void* is false, throws an error. :return: ("void", "") """ if not allow_void: msg = "Void not allowed" raise FtnError(msg, **vars(self)) return ("void", "")
[docs] def gen_access_expr(self, src_expr, iter_lvl) -> None: """A void element has no access expresison.""" return None
def _gen_marshalling(self, buf_ptr, acc_expr, iter_lvl, inverse=False): return [] class Schema(ftn.Void.Schema): @post_load def construct(self, data, **kwargs): return Void(**data)
# @with_schema(ftn.Atom.Schema) # class Atom(ftn.Atom, TypeABC): # def gen_ctype_expr(self, name, allow_void=False): # return ("DFL_atom_t", "") # def gen_access_expr(self, src_expr, iter_lvl): # # TODO: Something more is probably needed here to manage byte order # # differences when transferring atoms, probably the same as integer # # needs. # return [([], [], src_expr, self.readonly)] # def _gen_marshalling(self, buf_ptr, acc_expr, iter_lvl, inverse=False): # return [] # @with_schema(ftn.Integer.Schema)
[docs]class Integer(ftn.Integer, TypeABC): """C integer type."""
[docs] def requirements(self) -> Set[str]: """This representation uses explicit integer types as defined in <inttypes.h>. Bit-swap functions are defined in the in-house header file 'DFL_util.h' found in the 'assets' folder. """ return set(["<inttypes.h>", '"DFL_util.h"'])
def _range_type_attrs(self): rl, rh = self.range.values() for low, high, attrs in INT_ATTRS_TABLE: if rl >= low and rh <= high: return attrs raise FtnError(f"Invalid range specified: {self.range}", **vars(self)) def _type_attrs(self): unsigned, size = self._range_type_attrs() if self.bit_size: if self.bit_size.value < size: msg = f"Range {self.range} does not fit in bit_size {self.bit_size}" raise FtnError(msg, **vars(self)) size = self.bit_size.value endian = self.endian reverse = bool(endian and endian != TARGET_ENDIAN) return (reverse, unsigned, size) def _choose_repr(self): reverse, unsigned, size = self._type_attrs() hwi_type = INT_ATTRS_TO_STR[(unsigned, size)] if reverse and size > 8: repr_type = INT_ATTRS_TO_STR[(True, size)] sign_cmt = "" if unsigned else f"represents {hwi_type}," return f"{repr_type}/*{sign_cmt}reverse byteorder*/" # return REVERSE_INT_TO_STR[(unsigned, size)] else: return hwi_type
[docs] def gen_ctype_expr(self, name: str, allow_void=False) -> Tuple[str, str]: """Chooses an appropriate type representation for this integer based on its properties. :return: ("<expr>", "") """ # TODO: Something needs to be done to support more fine-grained bit- # sizes for integers (and other types), for instance if we want to # transmit a large array with values that would fit into 4 bits each. # This then involves interaction between the Array and Integer types. # The same with Structure which should result in bit-fields. return (self._choose_repr(), "")
[docs] def gen_c_value(self, value) -> str: """Normalizes a given value to a C integer value representation.""" internal_value = super().normalize_value(value) if internal_value is None: msg = f"Could not normalize value '{value}'" raise FtnError(msg, **vars(self)) return str(internal_value)
[docs] def gen_access_expr(self, src_expr: str , iter_lvl: int ) -> List[Tuple[List, List, str, str]]: """Generates the appropriate access expression for this integer based on its properties (e.g., endianness). :arg src_expr: arbitrary name for the variable in the access macro. :arg iter_lvl: how deep the current index level is. :return: singleton list witn a 4-tuple ([], [], access-expr, read-only) """ prefix, suffix = INT_SWAP_FUNCS[self._type_attrs()] return [([], [], prefix + src_expr + suffix, self.readonly)]
def _gen_marshalling(self, buf_ptr, acc_expr, iter_lvl, inverse=False): return [] def _gen_base_size_expr(self, acc_expr): return f"sizeof({self._choose_repr()})" class Schema(ftn.Integer.Schema): @post_load def construct(self, data, **kwargs): return Integer(**data)
# @with_schema(ftn.Boolean.Schema)
[docs]class Boolean(ftn.Boolean, TypeABC): """Boolean C type."""
[docs] def requirements(self) -> Set[str]: """This representation uses the "bool" type as defined in <stdbool.h> """ return set(["<stdbool.h>"])
[docs] def gen_ctype_expr(self, name, allow_void=False) -> Tuple[str, str]: """:return: ("bool", "")""" return ("bool", "")
[docs] def gen_c_value(self, value) -> str: """Normalizes a given value to a C bool representation.""" internal_value = super().normalize_value(value) if internal_value is None: msg = f"Could not normalize value '{value}'" raise FtnError(msg, **vars(self)) return "true" if internal_value else "false"
[docs] def gen_access_expr(self, src_expr: str , iter_lvl: int ) -> List[Tuple[List, List, str, str]]: """Returns access expression. Relevant for complex caller types.""" return [([], [], src_expr, self.readonly)]
def _gen_marshalling(self, buf_ptr, acc_expr, iter_lvl, inverse=False): return [] def _gen_base_size_expr(self, acc_expr): return "sizeof(bool)" class Schema(ftn.Boolean.Schema): @post_load def construct(self, data, **kwargs): return Boolean(**data)
[docs]class FtnDb(ftn.FtnDb): __specs = { # tok.TYPE_ATOM: Atom.Schema(), tok.TYPE_VOID: Void.Schema(), tok.TYPE_BOOLEAN: Boolean.Schema(), tok.TYPE_INTEGER: Integer.Schema(), tok.TYPE_ARRAY: Array.Schema(), tok.TYPE_STRUCTURE: Structure.Schema(), tok.TYPE_REF: TypeRef.Schema(), } FTN_GETTER_PREFIX = "FTNC_get__" FTN_SETTER_PREFIX = "FTNC_put__" def __init__(self, *args, **kwargs): super(FtnDb, self).__init__(*args, **kwargs)
[docs] def requirements(self, types: List[ftn.Uid] = []) -> List[str]: """Parses through a list of given *types* and returns a set of their include requirements. If *types* not provided it searches through all defined types. """ if not types: types = self.loaded_types() requs = [self.get(ty).requirements() for ty in types] ret = set().union(*requs) ret.add("<stdlib.h>") return list(ret)
[docs] def gen_typename_expr(self, entry: ftn.Uid) -> str: """Generates the (prefix) type name for a loaded type. If the type is local, generates the C expression for that type.""" name = _mangle_to_C_name(entry) + "_t" if entry.module == "__local__": return self.get(entry).gen_ctype_expr(name, allow_void=True)[0] return name
[docs] def gen_decl(self, entry: ftn.Entry, var: str, usage=None, static=False) -> List[str]: """Generates the statements for declaring (and eventually initializing) a type. :arg usage: overrides the generated expression :arg static: prefixes the definition with the 'static' keyword """ name = _mangle_to_C_name(entry) + "_t" if usage is not None: ty_str, suffix = (usage, "") elif entry is not None and entry.module == "__local__": ty_str, suffix = self.get(entry).gen_ctype_expr(name, allow_void=True) elif entry is not None: ty_str = self.gen_typename_expr(entry) suffix = self.get(entry).gen_ctype_expr(ty_str, allow_void=True)[1] else: raise ValueError("Cannot generate type declaration") static_str = "static " if static else "" value_str = f" = {entry.value}" if entry.value else "" return [f"{static_str}{ty_str} {var}{suffix}{value_str}"]
[docs] def gen_typedef(self, entry: ftn.Uid, allow_void=False): """Generates a statement for a typedef expression.""" qname = _mangle_to_C_name(entry) prefix, suffix = self.get(entry).gen_ctype_expr(qname, allow_void=allow_void) typedef = f'/* FTN: Type definition for "{qname}_t" */\n' typedef += f"typedef {prefix} {qname}_t{suffix}" return [typedef]
[docs] def gen_access_macros_expr(self, entry: ftn.Uid, read_only: bool = False) -> List[str]: """Generates access macros for all elements of this type as newline-separated expressions (not statements).""" qname = _mangle_to_C_name(entry) hdr_lines = [f'/* FTN: Access macros for type "{qname}_t" */\n'] for expr_info in self.get(entry).gen_access_expr("(x)", 0): getset_names, getset_args, getset_expr, read_only_expr = expr_info name_components = [qname] + getset_names getter_args = ["x"] + getset_args getter_name = self.FTN_GETTER_PREFIX + "__".join(name_components) hdr_lines.append( f"#define {getter_name}({', '.join(getter_args)}) ({getset_expr})\n" ) if read_only or read_only_expr: continue setter_args = getter_args + ["v"] setter_name = self.FTN_SETTER_PREFIX + "__".join(name_components) hdr_lines.append( f"#define {setter_name}({', '.join(setter_args)}) ({getset_expr}=(v))\n" ) return hdr_lines
[docs] def access_dict(self, uid: Union[TypeABC, ftn.Uid], read_only: bool = False) -> Dict: """Returns the access expressions for each element in this type as a dictionary tree (i.e., JSON object) accessible, e.g., from within a template expander. """ def _recursive_dict(what, dct, lst, accessor): if not lst: return if len(lst) == 1: dct[lst[0]] = dct.get(lst[0], {}) dct[lst[0]][what] = accessor return dct[lst[0]] = dct.get(lst[0], {}) _recursive_dict(what, dct[lst[0]], lst[1:], accessor) if isinstance(uid, TypeABC): return {"_get": "", "_set": "FTNC_ASSIGN"} assert isinstance(uid, ftn.Uid) access_dict: Dict = {} qname = _mangle_to_C_name(uid) for getset_names, _, _, read_only_expr in self.get(uid).gen_access_expr("(x)", 0): name_components = [qname] + getset_names getter_name = self.FTN_GETTER_PREFIX + "__".join(name_components) if not getset_names: access_dict["_get"] = getter_name else: _recursive_dict("_get", access_dict, getset_names, getter_name) if read_only or read_only_expr: continue setter_name = self.FTN_SETTER_PREFIX + "__".join(name_components) if not getset_names: access_dict["_set"] = setter_name else: _recursive_dict("_set", access_dict, getset_names, setter_name) return access_dict