wizapp-stdlib/codegen/datatypes.py
2025-09-07 19:22:24 +01:00

504 lines
14 KiB
Python

from enum import Enum
from pathlib import Path
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"
BOOL = "b32"
CHAR = "char"
C8 = "c8"
C16 = "c16"
C32 = "c32"
I8 = "i8"
I16 = "i16"
I32 = "i32"
I64 = "i64"
U8 = "u8"
U16 = "u16"
U32 = "u32"
U64 = "u64"
F32 = "f32"
F64 = "f64"
F128 = "f128"
IPTR = "iptr"
UPTR = "uptr"
def __str__(self) -> str:
return self.value
class CQualifier(Enum):
NONE = ""
CONST = "const "
EXTERNAL = "external "
INTERNAL = "internal "
PERSISTENT = "persistent "
def __str__(self) -> str:
return self.value
class CPointerType(Enum):
NONE = ""
SINGLE = "*"
DOUBLE = "**"
def __str__(self) -> str:
return self.value
@dataclass
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(SerialisableDataclass):
name: str
value: Optional[int] = None
def __str__(self) -> str:
return self.name + "" if self.value is None else f" = {self.value}"
@dataclass
class CEnum(SerialisableDataclass):
name: str
values: List[CEnumVal]
typedef: bool = False
def __str__(self) -> str:
if self.typedef:
header = "typedef enum {\n"
footer = f"}} {self.name};\n"
else:
header = f"enum {self.name} {{\n"
footer = "};\n"
values = ""
for value in self.values:
values += f" {str(value)},\n"
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(SerialisableDataclass):
name: str
value: str
def __str__(self) -> str:
return f"#define {self.name} {self.value}\n"
@dataclass
class CStruct(SerialisableDataclass):
name: str
cargs: List["CArg"]
typedef_name: Optional[str] = None
def __str__(self) -> str:
return self.declare() + self.define()
def declare(self) -> str:
declaration = f"typedef struct {self.name} {self.typedef_name if self.typedef_name is not None else self.name};\n"
return declaration
def define(self):
definition = f"struct {self.name} {{\n"
args = ""
for arg in self.cargs:
args += f" {str(arg)};\n"
footer = "};\n"
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(SerialisableDataclass):
name: str
_type: CDataType
array: bool = False
pointer: CPointer = field(default_factory=CPointer)
qualifier: CQualifier = CQualifier.NONE
def __str__(self) -> str:
qualifier = str(self.qualifier)
_type = get_datatype_string(self._type) + " "
pointer = str(self.pointer)
array = "[]" if self.array else ""
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(SerialisableDataclass):
name: str
ret_type: CDataType
args: List[CArg]
body: str
pointer: CPointer = field(default_factory=CPointer)
qualifiers: List[CQualifier] = field(default_factory=list)
def __str__(self) -> str:
qualifiers = ""
for qualifier in self.qualifiers:
if qualifier == CQualifier.NONE:
continue
if len(qualifiers) > 0:
qualifiers += " "
qualifiers += f"{str(qualifier)}"
args = ""
for i, arg in enumerate(self.args):
args += f"{str(arg)}"
if i + 1 < len(self.args):
args += ", "
return qualifiers + get_datatype_string(self.ret_type) + " " + str(self.pointer) + self.name + f"({args})"
def declare(self) -> str:
return f"{str(self)};\n"
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(SerialisableDataclass):
header: Union[str, "CHeader"]
local: bool = False
same_dir: bool = False
def __str__(self) -> str:
if isinstance(self.header, CHeader):
name = f"{self.header.name}.{self.header.extension}"
else:
name = self.header
if self.local:
open_symbol = '"'
close_symbol = '"'
if self.same_dir:
name = f"./{name}"
else:
open_symbol = '<'
close_symbol = '>'
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(SerialisableDataclass):
name: str
extension: str
includes: List[CInclude] = field(default_factory=list)
types: List[CUserType] = field(default_factory=list)
funcs: List[CFunc] = field(default_factory=list)
decl_types: List[CStruct] = field(default_factory=list)
macros: List[CMacro] = field(default_factory=list)
c_macros: List[CMacro] = field(default_factory=list)
cpp_macros: List[CMacro] = field(default_factory=list)
def save(self, output_dir: Path):
self.includes.extend(
[
CInclude(
header=str(convert_to_relative(WAPP_SRC_ROOT / "common" / "aliases" / "aliases.h", output_dir)).replace("\\", "/"),
local=True,
),
CInclude(
header=str(convert_to_relative(WAPP_SRC_ROOT / "common" / "platform" / "platform.h", output_dir)).replace("\\", "/"),
local=True,
)
]
)
output_file = output_dir / f"{self.name}.{self.extension}"
with open(output_file, "w+") as outfile:
outfile.write(str(self))
def __str__(self) -> str:
return """\
/**
* THIS FILE IS AUTOMATICALLY GENERATED. ANY MODIFICATIONS TO IT WILL BE OVERWRITTEN.
*/
"""
@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):
extension: str = "h"
def __str__(self) -> str:
name_upper = self.name.upper()
header_guard_name = f"{name_upper}_H"
header_guard_open = f"#ifndef {header_guard_name}\n#define {header_guard_name}\n\n"
header_guard_close = f"#endif // !{header_guard_name}\n"
c_linkage_open = "#ifdef WAPP_PLATFORM_CPP\nBEGIN_C_LINKAGE\n#endif // !WAPP_PLATFORM_CPP\n\n"
c_linkage_close = "\n#ifdef WAPP_PLATFORM_CPP\nEND_C_LINKAGE\n#endif // !WAPP_PLATFORM_CPP\n\n"
includes = _get_includes_string(self.includes)
macros = ""
for macro in self.macros:
macros += str(macro)
if len(macros) > 0:
macros += "\n"
if len(self.cpp_macros) > 0:
macros += "#ifdef WAPP_PLATFORM_CPP\n"
for macro in self.cpp_macros:
macros += str(macro)
macros += "#else\n"
for macro in self.c_macros:
macros += str(macro)
macros += "#endif // !WAPP_PLATFORM_CPP\n\n"
forward_declarations = ""
for _type in self.decl_types:
forward_declarations += _type.declare()
if len(forward_declarations) > 0:
forward_declarations += "\n"
types = ""
for _type in self.types:
types += str(_type) + "\n"
funcs = ""
for func in self.funcs:
funcs += func.declare()
return (
super().__str__() +
header_guard_open +
includes +
c_linkage_open +
macros +
forward_declarations +
types +
funcs +
c_linkage_close +
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):
extension: str = "c"
internal_funcs: List[CFunc] = field(default_factory=list)
def __str__(self) -> str:
includes = _get_includes_string(self.includes)
macros = ""
for macro in self.macros:
macros += str(macro)
if len(macros) > 0:
macros += "\n"
forward_declarations = ""
for _type in self.decl_types:
forward_declarations += _type.declare()
if len(forward_declarations) > 0:
forward_declarations += "\n"
types = ""
for _type in self.types:
types += str(_type) + "\n"
internal_funcs_decl = ""
internal_funcs_def = ""
for func in self.internal_funcs:
internal_funcs_decl += func.declare()
internal_funcs_def += func.define()
if len(internal_funcs_decl) > 0:
internal_funcs_decl += "\n"
funcs = ""
for func in self.funcs:
funcs += func.define()
return (
super().__str__() +
includes +
macros +
forward_declarations +
types +
internal_funcs_decl +
funcs +
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):
return str(_type)
elif isinstance(_type, CStruct) or isinstance(_type, CEnum):
return _type.name
elif isinstance(_type, str):
return _type
def _get_includes_string(includes: List[CInclude]) -> str:
output = ""
for include in sorted(includes, key=lambda inc: inc.local, reverse=True):
output += str(include)
if len(output) > 0:
output += "\n"
return output