From fbe4a5eeab2107dfe03fc097bc1f9627b222adbd Mon Sep 17 00:00:00 2001 From: aqua Date: Sun, 22 Oct 2023 18:42:58 +0300 Subject: Initial commit --- .gitignore | 2 ++ 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 +++++++++++++++++++++ interface_generator/__main__.py | 16 ++++++++++++ stdio.h | 16 ++++++++++++ stdio.yaml | 17 +++++++++++++ 11 files changed, 269 insertions(+) create mode 100644 .gitignore 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 create mode 100644 interface_generator/__main__.py create mode 100644 stdio.h create mode 100644 stdio.yaml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d8a1bb0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +__pycache__ +.coverage 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)) diff --git a/interface_generator/__main__.py b/interface_generator/__main__.py new file mode 100644 index 0000000..532edbc --- /dev/null +++ b/interface_generator/__main__.py @@ -0,0 +1,16 @@ +from argparse import ArgumentParser +from codegen import Header, parse_yaml + +parser = ArgumentParser( + prog='interface_generator', + description='Generate a C header from interface definition', +) +parser.add_argument('-i', '--input', required=True) +parser.add_argument('-o', '--output', required=True) + +args = parser.parse_args() + +with Header(args.output) as hdr: + parse_yaml(args.input, hdr) + hdr.print_c_header() + diff --git a/stdio.h b/stdio.h new file mode 100644 index 0000000..c6e4d80 --- /dev/null +++ b/stdio.h @@ -0,0 +1,16 @@ +#ifndef STDIO_H +#define STDIO_H + +typedef struct { + int (*putc)(uint32_t id, char c); + int (*puts)(uint32_t id, const char* s); + uint32_t id; +} FILE; + +const FILE *stdin; +const FILE *stdout; +const FILE *stderr; + +void printf(const char *__restrict__ format, ...); + +#endif // STDIO_H diff --git a/stdio.yaml b/stdio.yaml new file mode 100644 index 0000000..b7dcfb9 --- /dev/null +++ b/stdio.yaml @@ -0,0 +1,17 @@ + +includes: + stdint.h + +structs: + - name: FILE + putc: { type: fn_ptr, result: int, args: { id: uint32_t, c: char } } + puts: { type: fn_ptr, result: int, args: { id: uint32_t, s: const char* } } + id: { type: uint32_t } + +variables: + stdin: { type: const FILE * } + stdout: { type: const FILE * } + stderr: { type: const FILE * } + +functions: + printf: { result: void, args: { format: const char *__restrict__, ...: } } -- cgit v1.2.1