summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoraqua <aqua@iserlohn-fortress.net>2023-10-22 18:42:58 +0300
committeraqua <aqua@iserlohn-fortress.net>2023-10-22 18:42:58 +0300
commitfbe4a5eeab2107dfe03fc097bc1f9627b222adbd (patch)
tree11dd6b18e324721b1b3f23ed3617ac6922774ea7
downloadcodegen-fbe4a5eeab2107dfe03fc097bc1f9627b222adbd.tar.xz
Initial commitHEADmaster
-rw-r--r--.gitignore2
-rw-r--r--codegen/__init__.py10
-rw-r--r--codegen/cgen.py41
-rw-r--r--codegen/header.py54
-rw-r--r--codegen/test_cgen.py39
-rw-r--r--codegen/test_header.py17
-rw-r--r--codegen/types.py30
-rw-r--r--codegen/yaml.py27
-rw-r--r--interface_generator/__main__.py16
-rw-r--r--stdio.h16
-rw-r--r--stdio.yaml17
11 files changed, 269 insertions, 0 deletions
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__, ...: } }