Update codegen to support accepting JSON file as input

This commit is contained in:
Abdelrahman Said 2025-08-31 20:56:49 +01:00
parent 1e224702a3
commit 26fd329caa
6 changed files with 252 additions and 28 deletions

View File

@ -1,23 +1,60 @@
import json
from typing import Dict 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.dbl_list.make_dbl_list import DblListData, make_dbl_list
from codegen.array.make_array import ArrayData, make_array from codegen.array.make_array import ArrayData, make_array
def main(): def main(types_file: Path | None):
gen_dbl_list() dbl_list_datatypes: Dict[CDataType, DblListData] = {}
gen_array() 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(): if dbl_list_data is not None and isinstance(dbl_list_data, dict):
datatypes: Dict[CDataType, DblListData] = {} dbl_list_datatypes = {k: DblListData.from_dict(v) for k, v in dbl_list_data.items()}
make_dbl_list(datatypes)
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(): make_dbl_list(dbl_list_datatypes)
datatypes: Dict[CDataType, ArrayData] = {} make_array(array_datatypes)
make_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__": 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)

View File

@ -1,6 +1,6 @@
from pathlib import Path from pathlib import Path
from dataclasses import dataclass, field 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.constants import WAPP_SRC_ROOT
from codegen.utils import load_func_body_from_file, convert_to_relative from codegen.utils import load_func_body_from_file, convert_to_relative
from codegen.datatypes import ( from codegen.datatypes import (
@ -16,16 +16,24 @@ from codegen.datatypes import (
CPointerType, CPointerType,
CQualifier, CQualifier,
CInclude, CInclude,
SerialisableDataclass,
get_datatype_string, get_datatype_string,
) )
@dataclass @dataclass
class ArrayData: class ArrayData(SerialisableDataclass):
array_typename: str array_typename: str
hdr_decl_types: List[CStruct] = field(default_factory=list) hdr_decl_types: List[CStruct] = field(default_factory=list)
src_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 make_array(user_datatypes: Dict[CDataType, ArrayData] = {}):
def __format_func_body( def __format_func_body(

View File

@ -1,5 +1,10 @@
from pathlib import Path from pathlib import Path
# Paths
PACKAGE_DIR = Path(__file__).parent.resolve() 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"

View File

@ -1,11 +1,69 @@
from enum import Enum from enum import Enum
from pathlib import Path from pathlib import Path
from typing import Optional, Union, List from typing import Optional, Union, List, Dict, Type, Any, TypeVar, cast
from dataclasses import dataclass, field from dataclasses import dataclass, asdict, field, fields
from codegen.constants import WAPP_SRC_ROOT from codegen.constants import WAPP_SRC_ROOT
from codegen.utils import convert_to_relative 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): class CType(Enum):
VOID = "void" VOID = "void"
@ -53,16 +111,23 @@ class CPointerType(Enum):
@dataclass @dataclass
class CPointer: class CPointer(SerialisableDataclass):
_type: CPointerType = CPointerType.NONE _type: CPointerType = CPointerType.NONE
qualifier: CQualifier = CQualifier.NONE qualifier: CQualifier = CQualifier.NONE
def __str__(self) -> str: def __str__(self) -> str:
return str(self._type) + str(self.qualifier) 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 @dataclass
class CEnumVal: class CEnumVal(SerialisableDataclass):
name: str name: str
value: Optional[int] = None value: Optional[int] = None
@ -71,7 +136,7 @@ class CEnumVal:
@dataclass @dataclass
class CEnum: class CEnum(SerialisableDataclass):
name: str name: str
values: List[CEnumVal] values: List[CEnumVal]
typedef: bool = False typedef: bool = False
@ -90,9 +155,15 @@ class CEnum:
return header + values + footer 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 @dataclass
class CMacro: class CMacro(SerialisableDataclass):
name: str name: str
value: str value: str
@ -101,7 +172,7 @@ class CMacro:
@dataclass @dataclass
class CStruct: class CStruct(SerialisableDataclass):
name: str name: str
cargs: List["CArg"] cargs: List["CArg"]
typedef_name: Optional[str] = None typedef_name: Optional[str] = None
@ -122,13 +193,19 @@ class CStruct:
return definition + args + footer; 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] CUserType = Union[CStruct, CEnum]
CDataType = Union[CType, CUserType, str] CDataType = Union[CType, CUserType, str]
@dataclass @dataclass
class CArg: class CArg(SerialisableDataclass):
name: str name: str
_type: CDataType _type: CDataType
array: bool = False array: bool = False
@ -143,9 +220,21 @@ class CArg:
return qualifier + _type + pointer + self.name + array 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 @dataclass
class CFunc: class CFunc(SerialisableDataclass):
name: str name: str
ret_type: CDataType ret_type: CDataType
args: List[CArg] args: List[CArg]
@ -176,9 +265,21 @@ class CFunc:
def define(self) -> str: def define(self) -> str:
return f"{str(self)} {{\n{self.body}\n}}\n\n" 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 @dataclass
class CInclude: class CInclude(SerialisableDataclass):
header: Union[str, "CHeader"] header: Union[str, "CHeader"]
local: bool = False local: bool = False
same_dir: bool = False same_dir: bool = False
@ -201,9 +302,18 @@ class CInclude:
return f"#include {open_symbol}{name}{close_symbol}\n" 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 @dataclass
class CFile: class CFile(SerialisableDataclass):
name: str name: str
extension: str extension: str
includes: List[CInclude] = field(default_factory=list) 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 @dataclass
class CHeader(CFile): class CHeader(CFile):
@ -297,6 +423,10 @@ class CHeader(CFile):
header_guard_close header_guard_close
) )
@classmethod
def from_dict(cls: Type["CHeader"], d: Dict[str, Any]) -> "CHeader":
return cast("CHeader", super().from_dict(d))
@dataclass @dataclass
class CSource(CFile): class CSource(CFile):
@ -346,6 +476,13 @@ class CSource(CFile):
internal_funcs_def 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: def get_datatype_string(_type: CDataType) -> str:
if isinstance(_type, CType): if isinstance(_type, CType):

View File

@ -1,6 +1,6 @@
from pathlib import Path from pathlib import Path
from dataclasses import dataclass, field 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.constants import WAPP_SRC_ROOT
from codegen.utils import load_func_body_from_file, convert_to_relative from codegen.utils import load_func_body_from_file, convert_to_relative
from codegen.datatypes import ( from codegen.datatypes import (
@ -16,17 +16,25 @@ from codegen.datatypes import (
CPointerType, CPointerType,
CQualifier, CQualifier,
CInclude, CInclude,
SerialisableDataclass,
get_datatype_string, get_datatype_string,
) )
@dataclass @dataclass
class DblListData: class DblListData(SerialisableDataclass):
node_typename: str node_typename: str
list_typename: str list_typename: str
hdr_decl_types: List[CStruct] = field(default_factory=list) hdr_decl_types: List[CStruct] = field(default_factory=list)
src_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 make_dbl_list(user_datatypes: Dict[CDataType, DblListData] = {}):
def __format_func_body( def __format_func_body(

View File

@ -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": []
}
}
}