From 26fd329caa887bc3c3a797216e50c851279968ff Mon Sep 17 00:00:00 2001 From: Abdelrahman Date: Sun, 31 Aug 2025 20:56:49 +0100 Subject: [PATCH] Update codegen to support accepting JSON file as input --- codegen/__main__.py | 59 ++++++++--- codegen/array/make_array.py | 12 ++- codegen/constants.py | 9 +- codegen/datatypes.py | 159 +++++++++++++++++++++++++++--- codegen/dbl_list/make_dbl_list.py | 12 ++- codegen_custom_data_example.json | 29 ++++++ 6 files changed, 252 insertions(+), 28 deletions(-) create mode 100644 codegen_custom_data_example.json diff --git a/codegen/__main__.py b/codegen/__main__.py index e9fb70b..e2ea776 100644 --- a/codegen/__main__.py +++ b/codegen/__main__.py @@ -1,23 +1,60 @@ +import json from typing import Dict -from codegen.datatypes import CDataType +from pathlib import Path +from codegen.datatypes import CDataType, CStruct +from codegen.constants import WAPP_REPO_ROOT, DBL_LIST_DATA, ARRAY_DATA from codegen.dbl_list.make_dbl_list import DblListData, make_dbl_list from codegen.array.make_array import ArrayData, make_array -def main(): - gen_dbl_list() - gen_array() +def main(types_file: Path | None): + dbl_list_datatypes: Dict[CDataType, DblListData] = {} + array_datatypes: Dict[CDataType, ArrayData] = {} + if types_file is not None: + with types_file.open("r") as infile: + datatypes = json.load(infile) + dbl_list_data = datatypes.get(DBL_LIST_DATA) + array_data = datatypes.get(ARRAY_DATA) -def gen_dbl_list(): - datatypes: Dict[CDataType, DblListData] = {} - make_dbl_list(datatypes) + if dbl_list_data is not None and isinstance(dbl_list_data, dict): + dbl_list_datatypes = {k: DblListData.from_dict(v) for k, v in dbl_list_data.items()} + if array_data is not None and isinstance(array_data, dict): + array_datatypes = {k: ArrayData.from_dict(v) for k, v in array_data.items()} -def gen_array(): - datatypes: Dict[CDataType, ArrayData] = {} - make_array(datatypes) + make_dbl_list(dbl_list_datatypes) + make_array(array_datatypes) + + # Save example types file + custom_struct = CStruct(name="custom_type", cargs=[], typedef_name="CustomType") + example = { + DBL_LIST_DATA: { + "CustomType": DblListData( + node_typename="CustomTypeNode", + list_typename="CustomTypeList", + hdr_decl_types=[custom_struct], + ).to_dict() + }, + ARRAY_DATA: { + "CustomType": ArrayData( + array_typename="CustomTypeArray", + hdr_decl_types=[custom_struct], + ).to_dict() + }, + } + + example_file = WAPP_REPO_ROOT / "codegen_custom_data_example.json" + with example_file.open("w") as outfile: + json.dump(example, outfile, indent=2) if __name__ == "__main__": - main() + from argparse import ArgumentParser + + parser = ArgumentParser() + parser.add_argument("-f", "--types-file", type=Path, help="JSON file containing custom types for codegen") + + args = parser.parse_args() + + main(args.types_file) diff --git a/codegen/array/make_array.py b/codegen/array/make_array.py index e395e34..eb0a66b 100644 --- a/codegen/array/make_array.py +++ b/codegen/array/make_array.py @@ -1,6 +1,6 @@ from pathlib import Path from dataclasses import dataclass, field -from typing import List, Dict +from typing import List, Dict, Any, Type from codegen.constants import WAPP_SRC_ROOT from codegen.utils import load_func_body_from_file, convert_to_relative from codegen.datatypes import ( @@ -16,16 +16,24 @@ from codegen.datatypes import ( CPointerType, CQualifier, CInclude, + SerialisableDataclass, get_datatype_string, ) @dataclass -class ArrayData: +class ArrayData(SerialisableDataclass): array_typename: str hdr_decl_types: List[CStruct] = field(default_factory=list) src_decl_types: List[CStruct] = field(default_factory=list) + @classmethod + def from_dict(cls: Type["ArrayData"], d: Dict[str, Any]) -> "ArrayData": + data = ArrayData(**d) + data.hdr_decl_types = [CStruct.from_dict(v) for v in data.hdr_decl_types if isinstance(v, dict)] + data.src_decl_types = [CStruct.from_dict(v) for v in data.src_decl_types if isinstance(v, dict)] + return data + def make_array(user_datatypes: Dict[CDataType, ArrayData] = {}): def __format_func_body( diff --git a/codegen/constants.py b/codegen/constants.py index ebad100..29dc1c5 100644 --- a/codegen/constants.py +++ b/codegen/constants.py @@ -1,5 +1,10 @@ from pathlib import Path - +# Paths PACKAGE_DIR = Path(__file__).parent.resolve() -WAPP_SRC_ROOT = PACKAGE_DIR.parent / "src" +WAPP_REPO_ROOT = PACKAGE_DIR.parent +WAPP_SRC_ROOT = WAPP_REPO_ROOT / "src" + +# Dictionary Keys +DBL_LIST_DATA = "dbl_list_data" +ARRAY_DATA = "array_data" diff --git a/codegen/datatypes.py b/codegen/datatypes.py index 2641022..0e7b8e7 100644 --- a/codegen/datatypes.py +++ b/codegen/datatypes.py @@ -1,11 +1,69 @@ from enum import Enum from pathlib import Path -from typing import Optional, Union, List -from dataclasses import dataclass, field +from typing import Optional, Union, List, Dict, Type, Any, TypeVar, cast +from dataclasses import dataclass, asdict, field, fields from codegen.constants import WAPP_SRC_ROOT from codegen.utils import convert_to_relative +E = TypeVar("E", bound="Enum") +S = TypeVar("S", bound="SerialisableDataclass") +F = TypeVar("F", bound="CFile") + +@dataclass +class SerialisableDataclass: + def to_dict(self) -> Dict[str, Any]: + d = asdict(self) + for f in fields(self): + member = getattr(self, f.name) + + if isinstance(member, list): + d[f.name] = [self.__serialise_member(i) for i in member] + else: + d[f.name] = self.__serialise_member(member) + + return d + + def __serialise_member(self, member: Any) -> Any: + if isinstance(member, Enum): + return member.value + elif isinstance(member, SerialisableDataclass): + return member.to_dict() + + return member + + @staticmethod + def to_enum_value(value: Any, _type: Type[E]) -> "E": + if isinstance(value, _type): + return value + + return _type(value) + + @staticmethod + def to_c_usertype(value: dict[str, Any]) -> "CUserType": + try: + output = CStruct.from_dict(value) + except TypeError: + output = CEnum.from_dict(value) + + return output + + @staticmethod + def to_cdatatype(value: Any) -> "CDataType": + if isinstance(value, dict): + output = SerialisableDataclass.to_c_usertype(value) + else: + try: + output = CType(value) + except ValueError: + output = value + + return output + + @classmethod + def from_dict(cls: Type[S], d: Dict[str, Any]) -> "S": + return cls(**d) + class CType(Enum): VOID = "void" @@ -53,16 +111,23 @@ class CPointerType(Enum): @dataclass -class CPointer: +class CPointer(SerialisableDataclass): _type: CPointerType = CPointerType.NONE qualifier: CQualifier = CQualifier.NONE def __str__(self) -> str: return str(self._type) + str(self.qualifier) + @classmethod + def from_dict(cls: Type["CPointer"], d: Dict[str, Any]) -> "CPointer": + ptr = CPointer(**d) + ptr._type = CPointer.to_enum_value(ptr._type, CPointerType) + ptr.qualifier = CPointer.to_enum_value(ptr.qualifier, CQualifier) + return ptr + @dataclass -class CEnumVal: +class CEnumVal(SerialisableDataclass): name: str value: Optional[int] = None @@ -71,7 +136,7 @@ class CEnumVal: @dataclass -class CEnum: +class CEnum(SerialisableDataclass): name: str values: List[CEnumVal] typedef: bool = False @@ -90,9 +155,15 @@ class CEnum: return header + values + footer + @classmethod + def from_dict(cls: Type["CEnum"], d: Dict[str, Any]) -> "CEnum": + e = CEnum(**d) + e.values = [CEnumVal.from_dict(v) for v in e.values if isinstance(v, dict)] + return e + @dataclass -class CMacro: +class CMacro(SerialisableDataclass): name: str value: str @@ -101,7 +172,7 @@ class CMacro: @dataclass -class CStruct: +class CStruct(SerialisableDataclass): name: str cargs: List["CArg"] typedef_name: Optional[str] = None @@ -122,13 +193,19 @@ class CStruct: return definition + args + footer; + @classmethod + def from_dict(cls: Type["CStruct"], d: Dict[str, Any]) -> "CStruct": + s = CStruct(**d) + s.cargs = [CArg.from_dict(v) for v in s.cargs if isinstance(v, dict)] + return s + CUserType = Union[CStruct, CEnum] CDataType = Union[CType, CUserType, str] @dataclass -class CArg: +class CArg(SerialisableDataclass): name: str _type: CDataType array: bool = False @@ -143,9 +220,21 @@ class CArg: return qualifier + _type + pointer + self.name + array + @classmethod + def from_dict(cls: Type["CArg"], d: Dict[str, Any]) -> "CArg": + arg = CArg(**d) + arg._type = CArg.to_cdatatype(arg._type) + + if isinstance(arg.pointer, dict): + arg.pointer = CPointer.from_dict(arg.pointer) + + arg.qualifier = CArg.to_enum_value(arg.qualifier, CQualifier) + + return arg + @dataclass -class CFunc: +class CFunc(SerialisableDataclass): name: str ret_type: CDataType args: List[CArg] @@ -176,9 +265,21 @@ class CFunc: def define(self) -> str: return f"{str(self)} {{\n{self.body}\n}}\n\n" + @classmethod + def from_dict(cls: Type["CFunc"], d: Dict[str, Any]) -> "CFunc": + f = CFunc(**d) + f.ret_type = CFunc.to_cdatatype(f.ret_type) + f.args = [CArg.from_dict(v) for v in f.args if isinstance(v, dict)] + f.qualifiers = [CFunc.to_enum_value(v, CQualifier) for v in f.qualifiers] + + if isinstance(f.pointer, dict): + f.pointer = CPointer.from_dict(f.pointer) + + return f + @dataclass -class CInclude: +class CInclude(SerialisableDataclass): header: Union[str, "CHeader"] local: bool = False same_dir: bool = False @@ -201,9 +302,18 @@ class CInclude: return f"#include {open_symbol}{name}{close_symbol}\n" + @classmethod + def from_dict(cls: Type["CInclude"], d: Dict[str, Any]) -> "CInclude": + inc = CInclude(**d) + + if isinstance(inc.header, dict): + inc.header = CHeader.from_dict(inc.header) + + return inc + @dataclass -class CFile: +class CFile(SerialisableDataclass): name: str extension: str includes: List[CInclude] = field(default_factory=list) @@ -239,6 +349,22 @@ class CFile: """ + @classmethod + def from_dict(cls: Type["CFile"], d: Dict[str, Any]) -> "CFile": + f = CFile(**d) + f.deserialise_c_file_data() + + return f + + def deserialise_c_file_data(self) -> None: + self.includes = [CInclude.from_dict(v) for v in self.includes if isinstance(v, dict)] + self.types = [CFile.to_c_usertype(v) for v in self.types if isinstance(v, dict)] + self.funcs = [CFunc.from_dict(v) for v in self.funcs if isinstance(v, dict)] + self.decl_types = [CStruct.from_dict(v) for v in self.decl_types if isinstance(v, dict)] + self.macros = [CMacro.from_dict(v) for v in self.macros if isinstance(v, dict)] + self.c_macros = [CMacro.from_dict(v) for v in self.c_macros if isinstance(v, dict)] + self.cpp_macros = [CMacro.from_dict(v) for v in self.cpp_macros if isinstance(v, dict)] + @dataclass class CHeader(CFile): @@ -297,6 +423,10 @@ class CHeader(CFile): header_guard_close ) + @classmethod + def from_dict(cls: Type["CHeader"], d: Dict[str, Any]) -> "CHeader": + return cast("CHeader", super().from_dict(d)) + @dataclass class CSource(CFile): @@ -346,6 +476,13 @@ class CSource(CFile): internal_funcs_def ) + @classmethod + def from_dict(cls: Type["CSource"], d: Dict[str, Any]) -> "CSource": + s = CSource(**d) + s.deserialise_c_file_data() + s.internal_funcs = [CFunc.from_dict(v) for v in s.funcs if isinstance(v, dict)] + return s + def get_datatype_string(_type: CDataType) -> str: if isinstance(_type, CType): diff --git a/codegen/dbl_list/make_dbl_list.py b/codegen/dbl_list/make_dbl_list.py index 8eaa96f..4bcc167 100644 --- a/codegen/dbl_list/make_dbl_list.py +++ b/codegen/dbl_list/make_dbl_list.py @@ -1,6 +1,6 @@ from pathlib import Path from dataclasses import dataclass, field -from typing import List, Dict +from typing import List, Dict, Any, Type from codegen.constants import WAPP_SRC_ROOT from codegen.utils import load_func_body_from_file, convert_to_relative from codegen.datatypes import ( @@ -16,17 +16,25 @@ from codegen.datatypes import ( CPointerType, CQualifier, CInclude, + SerialisableDataclass, get_datatype_string, ) @dataclass -class DblListData: +class DblListData(SerialisableDataclass): node_typename: str list_typename: str hdr_decl_types: List[CStruct] = field(default_factory=list) src_decl_types: List[CStruct] = field(default_factory=list) + @classmethod + def from_dict(cls: Type["DblListData"], d: Dict[str, Any]) -> "DblListData": + data = DblListData(**d) + data.hdr_decl_types = [CStruct.from_dict(v) for v in data.hdr_decl_types if isinstance(v, dict)] + data.src_decl_types = [CStruct.from_dict(v) for v in data.src_decl_types if isinstance(v, dict)] + return data + def make_dbl_list(user_datatypes: Dict[CDataType, DblListData] = {}): def __format_func_body( diff --git a/codegen_custom_data_example.json b/codegen_custom_data_example.json new file mode 100644 index 0000000..536bfad --- /dev/null +++ b/codegen_custom_data_example.json @@ -0,0 +1,29 @@ +{ + "dbl_list_data": { + "CustomType": { + "node_typename": "CustomTypeNode", + "list_typename": "CustomTypeList", + "hdr_decl_types": [ + { + "name": "custom_type", + "cargs": [], + "typedef_name": "CustomType" + } + ], + "src_decl_types": [] + } + }, + "array_data": { + "CustomType": { + "array_typename": "CustomTypeArray", + "hdr_decl_types": [ + { + "name": "custom_type", + "cargs": [], + "typedef_name": "CustomType" + } + ], + "src_decl_types": [] + } + } +} \ No newline at end of file