From fbe4a5eeab2107dfe03fc097bc1f9627b222adbd Mon Sep 17 00:00:00 2001 From: aqua Date: Sun, 22 Oct 2023 18:42:58 +0300 Subject: Initial commit --- codegen/__init__.py | 10 ++++++++++ codegen/cgen.py | 41 ++++++++++++++++++++++++++++++++++++++ codegen/header.py | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++ codegen/test_cgen.py | 39 ++++++++++++++++++++++++++++++++++++ codegen/test_header.py | 17 ++++++++++++++++ codegen/types.py | 30 ++++++++++++++++++++++++++++ codegen/yaml.py | 27 +++++++++++++++++++++++++ 7 files changed, 218 insertions(+) create mode 100644 codegen/__init__.py create mode 100644 codegen/cgen.py create mode 100644 codegen/header.py create mode 100644 codegen/test_cgen.py create mode 100644 codegen/test_header.py create mode 100644 codegen/types.py create mode 100644 codegen/yaml.py (limited to 'codegen') diff --git a/codegen/__init__.py b/codegen/__init__.py new file mode 100644 index 0000000..ff3635f --- /dev/null +++ b/codegen/__init__.py @@ -0,0 +1,10 @@ +""" + codegen module +Contains utilities to parse yaml definitions and generate C headers +from them +""" +__license__ = 'GPL-3.0-only' + +from .types import Variable, Function, Struct +from .header import Header +from .yaml import read as parse_yaml diff --git a/codegen/cgen.py b/codegen/cgen.py new file mode 100644 index 0000000..0685a6d --- /dev/null +++ b/codegen/cgen.py @@ -0,0 +1,41 @@ +""" C header generation """ +__license__ = 'GPL-3.0-only' + +from .types import Variable, Function, Struct + +def variable_str(var: Variable): + return f'{var.typedef}{"" if var.typedef.endswith("*") else " "}{var.name};' + +def function_str(func: Function): + args = '' if func.args is None else ', '.join( + f'{T+" " if T is not None else ""}{param}' + for param, T in func.args.items()) + return f'{func.result} {func.name}({args});' + +def function_ptr_str(func: Function): + args = '' if func.args is None else ', '.join( + f'{T+" " if T is not None else ""}{param}' + for param, T in func.args.items()) + return f'{func.result} (*{func.name})({args});' + +def struct_str(struct: Struct): + members = '\n'.join([ f' {nested_object_str(it)}' for it in struct.members ]) + return f"""typedef struct {{ +{members} +}} {struct.name};""" + +def nested_object_str(obj): + if isinstance(obj, Variable): + return variable_str(obj) + if isinstance(obj, Function): + return function_ptr_str(obj) + return None + +def as_str(obj): + if isinstance(obj, Variable): + return variable_str(obj) + if isinstance(obj, Function): + return function_str(obj) + if isinstance(obj, Struct): + return struct_str(obj) + return None diff --git a/codegen/header.py b/codegen/header.py new file mode 100644 index 0000000..2ffbf6b --- /dev/null +++ b/codegen/header.py @@ -0,0 +1,54 @@ +""" Header serializer """ +__license__ = 'GPL-3.0-only' + +import re +import sys +from pathlib import Path +from .cgen import as_str + +class Header: + """ Python representation of a header """ + def __init__(self, name: str): + self.name = name + self.file = None + self.variables = [] + self.functions = [] + self.structs = [] + + def __enter__(self): + if self.name is None: + self.file = sys.stdout + else: + self.file = open(self.name, mode='w', encoding='utf-8') + return self + + def __exit__(self, exc_type, exc_value, traceback): + if self.file != sys.stdout: + self.file.close() + + def header_guard(self, mock=False): + """ Generate header guard from name """ + name = Path(self.name).stem + guard = '_'.join(re.findall('.[^A-Z]*', name)) + guard = guard.upper().replace('.', '_') + if mock: + return f'MOCK_{guard}_HH' + return f'{guard}_H' + + def _print(self, items): + for item in items: + print(as_str(item), file=self.file) + if len(items) > 0: + print('', file=self.file) + + def print_c_header(self): + """ Print C header """ + header_guard = self.header_guard() + print(f'#ifndef {header_guard}', file=self.file) + print(f'#define {header_guard}\n', file=self.file) + + self._print(self.structs) + self._print(self.variables) + self._print(self.functions) + + print(f'#endif // {header_guard}', file=self.file) diff --git a/codegen/test_cgen.py b/codegen/test_cgen.py new file mode 100644 index 0000000..6d1d4ef --- /dev/null +++ b/codegen/test_cgen.py @@ -0,0 +1,39 @@ +import unittest +from codegen.types import Variable, Function, Struct +from .cgen import as_str + +test_variable_list = [ + (Variable('i', typedef='int'), 'int i;'), +] + +test_function_list = [ + (Function('f', result='void'), 'void f();'), + (Function('sum', result='uint32_t', args={'a': 'uint32_t', 'b': 'uint32_t'}), + 'uint32_t sum(uint32_t a, uint32_t b);'), + (Function('printf', result='void', args={'format': 'const char *restrict', '...': None}), + 'void printf(const char *restrict format, ...);'), +] + +test_struct_list = [ + (Struct('xyz', [ Variable('i', typedef='int'), Function('f', result='void') ]), +"""typedef struct { + int i; + void (*f)(); +} xyz;"""), +] + +class AsStrUnittests(unittest.TestCase): + def test_as_str_variable(self): + for item, expect in test_variable_list: + with self.subTest(expect): + self.assertEqual(as_str(item), expect) + + def test_as_str_function(self): + for item, expect in test_function_list: + with self.subTest(expect): + self.assertEqual(as_str(item), expect) + + def test_as_str_struct(self): + for item, expect in test_struct_list: + with self.subTest(expect): + self.assertEqual(as_str(item), expect) diff --git a/codegen/test_header.py b/codegen/test_header.py new file mode 100644 index 0000000..233845e --- /dev/null +++ b/codegen/test_header.py @@ -0,0 +1,17 @@ +import unittest +from .header import Header + +class HeaderUnittests(unittest.TestCase): + def test_c_header_guard(self): + hdr = Header('stdio.yaml') + self.assertEqual('STDIO_H', hdr.header_guard()) + + hdr = Header('LinkedListAllocator.yaml') + self.assertEqual('LINKED_LIST_ALLOCATOR_H', hdr.header_guard()) + + def test_mock_header_guard(self): + hdr = Header('stdio.yaml') + self.assertEqual('MOCK_STDIO_HH', hdr.header_guard(mock=True)) + + hdr = Header('LinkedListAllocator.yaml') + self.assertEqual('MOCK_LINKED_LIST_ALLOCATOR_HH', hdr.header_guard(mock=True)) diff --git a/codegen/types.py b/codegen/types.py new file mode 100644 index 0000000..3b8b85d --- /dev/null +++ b/codegen/types.py @@ -0,0 +1,30 @@ +""" codegen types """ +__license__ = 'GPL-3.0-only' + +class Variable: + def __init__(self, name, data=None, typedef=None): + self.name = name + self.typedef = typedef + self.comment = None + if data is not None: + for key, value in data.items(): + setattr(self, key, value) + +class Function: + def __init__(self, name, data=None, result=None, args=None): + self.name = name + self.result = result + self.args = args + self.comment = None + if data is not None: + for key, value in data.items(): + setattr(self, key, value) + +class Struct: + def __init__(self, name, members): + self.name = name + self.members = members + self.comment = None + +# TODO enum +# TODO typedef diff --git a/codegen/yaml.py b/codegen/yaml.py new file mode 100644 index 0000000..15ee7a4 --- /dev/null +++ b/codegen/yaml.py @@ -0,0 +1,27 @@ +""" yaml parser """ +__license__ = 'GPL-3.0-only' + +import yaml +from .types import Variable, Function, Struct +from .header import Header + +def read(path: str, hdr: Header): + with open(path, mode='r', encoding='utf-8') as stream: + data = yaml.safe_load(stream) + + for item, info in data['variables'].items(): + hdr.variables.append(Variable(item, data=info)) + + for item, info in data['functions'].items(): + hdr.functions.append(Function(item, data=info)) + + for struct in data['structs']: + name = struct.pop('name') + members = [] + for item, info in struct.items(): + match info['type']: + case 'fn_ptr': + members.append(Function(item, data=info)) + case _: + members.append(Variable(item, data=info)) + hdr.structs.append(Struct(name, members)) -- cgit v1.2.1