Swift: split generated C++ code into .h and .cpp

This commit is contained in:
Paolo Tranquilli
2022-06-23 10:01:47 +02:00
parent 3248f7b423
commit b2ebf63d2e
14 changed files with 114 additions and 59 deletions

View File

@@ -78,4 +78,4 @@ def generate(opts, renderer):
assert opts.cpp_output
processor = Processor({cls.name: cls for cls in schema.load(opts.schema).classes})
out = opts.cpp_output
renderer.render(cpp.ClassList(processor.get_classes(), opts.schema), out / f"TrapClasses.h")
renderer.render(cpp.ClassList(processor.get_classes(), opts.schema), out / "TrapClasses")

View File

@@ -80,8 +80,7 @@ def generate(opts, renderer):
for d in e.rhs:
tag_graph.setdefault(d.type, set()).add(e.lhs)
renderer.render(cpp.TrapList(traps, opts.dbscheme),
out / f"TrapEntries.h")
renderer.render(cpp.TrapList(traps, opts.dbscheme), out / "TrapEntries")
tags = []
for index, tag in enumerate(toposort_flatten(tag_graph)):
@@ -91,4 +90,4 @@ def generate(opts, renderer):
index=index,
id=tag,
))
renderer.render(cpp.TagList(tags, opts.dbscheme), out / f"TrapTags.h")
renderer.render(cpp.TagList(tags, opts.dbscheme), out / "TrapTags")

View File

@@ -99,7 +99,7 @@ class Tag:
@dataclass
class TrapList:
template: ClassVar = 'trap_traps'
extensions = ["h", "cpp"]
traps: List[Trap]
source: str
@@ -107,6 +107,7 @@ class TrapList:
@dataclass
class TagList:
template: ClassVar = 'trap_tags'
extensions = ["h"]
tags: List[Tag]
source: str
@@ -143,6 +144,7 @@ class Class:
@dataclass
class ClassList:
template: ClassVar = "cpp_classes"
extensions: ClassVar = ["h", "cpp"]
classes: List[Class]
source: str

View File

@@ -28,18 +28,23 @@ class Renderer:
`data` must have a `template` attribute denoting which template to use from the template directory.
If the file is unchanged, then no write is performed (and `done_something` remains unchanged)
If `guard_base` is provided, it must be a path at the root of `output` and a header guard will be injected in
the template based off of the relative path of `output` in `guard_base`
Optionally, `data` can also have an `extensions` attribute denoting list of file extensions: they will all be
appended to the template name with an underscore and be generated in turn.
"""
mnemonic = type(data).__name__
output.parent.mkdir(parents=True, exist_ok=True)
data = self._r.render_name(data.template, data, generator=self._generator)
with open(output, "w") as out:
out.write(data)
log.debug(f"generated {mnemonic} {output.name}")
self.written.add(output)
extensions = getattr(data, "extensions", [None])
for ext in extensions:
output_filename = output
template = data.template
if ext:
output_filename = output_filename.with_suffix(f".{ext}")
template += f"_{ext}"
contents = self._r.render_name(template, data, generator=self._generator)
with open(output_filename, "w") as out:
out.write(contents)
log.debug(f"{mnemonic}: generated {output.name}")
self.written.add(output_filename)
def cleanup(self, existing):
""" Remove files in `existing` for which no `render` has been called """

View File

@@ -0,0 +1,37 @@
// generated by {{generator}} from {{source}}
// clang-format off
#include "./TrapClasses.h"
namespace codeql {
{{#classes}}
void {{name}}::emit({{^final}}TrapLabel<{{name}}Tag> id, {{/final}}std::ostream& out) const {
{{#trap_name}}
out << {{.}}Trap{id{{#single_fields}}, {{field_name}}{{/single_fields}}} << '\n';
{{/trap_name}}
{{#bases}}
{{ref.name}}::emit(id, out);
{{/bases}}
{{#fields}}
{{#is_predicate}}
if ({{field_name}}) out << {{trap_name}}Trap{id} << '\n';
{{/is_predicate}}
{{#is_optional}}
{{^is_repeated}}
if ({{field_name}}) out << {{trap_name}}Trap{id, *{{field_name}}} << '\n';
{{/is_repeated}}
{{/is_optional}}
{{#is_repeated}}
for (auto i = 0u; i < {{field_name}}.size(); ++i) {
{{^is_optional}}
out << {{trap_name}}Trap{id, i, {{field_name}}[i]};
{{/is_optional}}
{{#is_optional}}
if ({{field_name}}[i]) out << {{trap_name}}Trap{id, i, *{{field_name}}[i]};
{{/is_optional}}
}
{{/is_repeated}}
{{/fields}}
}
{{/classes}}
}

View File

@@ -31,34 +31,7 @@ struct {{name}}{{#has_bases}} : {{#bases}}{{^first}}, {{/first}}{{ref.name}}{{/b
{{/final}}
protected:
void emit({{^final}}TrapLabel<{{name}}Tag> id, {{/final}}std::ostream& out) const {
{{#trap_name}}
out << {{.}}Trap{id{{#single_fields}}, {{field_name}}{{/single_fields}}} << '\n';
{{/trap_name}}
{{#bases}}
{{ref.name}}::emit(id, out);
{{/bases}}
{{#fields}}
{{#is_predicate}}
if ({{field_name}}) out << {{trap_name}}Trap{id} << '\n';
{{/is_predicate}}
{{#is_optional}}
{{^is_repeated}}
if ({{field_name}}) out << {{trap_name}}Trap{id, *{{field_name}}} << '\n';
{{/is_repeated}}
{{/is_optional}}
{{#is_repeated}}
for (auto i = 0u; i < {{field_name}}.size(); ++i) {
{{^is_optional}}
out << {{trap_name}}Trap{id, i, {{field_name}}[i]};
{{/is_optional}}
{{#is_optional}}
if ({{field_name}}[i]) out << {{trap_name}}Trap{id, i, *{{field_name}}[i]};
{{/is_optional}}
}
{{/is_repeated}}
{{/fields}}
}
void emit({{^final}}TrapLabel<{{name}}Tag> id, {{/final}}std::ostream& out) const;
};
template <>

View File

@@ -0,0 +1,15 @@
// generated by {{generator}} from {{source}}
// clang-format off
#include "./TrapEntries.h"
namespace codeql {
{{#traps}}
// {{table_name}}
std::ostream &operator<<(std::ostream &out, const {{name}}Trap &e) {
out << "{{table_name}}("{{#fields}}{{^first}} << ", "{{/first}}
<< {{#get_streamer}}e.{{field_name}}{{/get_streamer}}{{/fields}} << ")";
return out;
}
{{/traps}}
}

View File

@@ -14,21 +14,12 @@ namespace codeql {
// {{table_name}}
struct {{name}}Trap {
static constexpr bool is_binding = {{#id}}true{{/id}}{{^id}}false{{/id}};
{{#id}}
{{type}} getBoundLabel() const { return {{field_name}}; }
{{/id}}
{{#fields}}
{{type}} {{field_name}}{};
{{/fields}}
};
inline std::ostream &operator<<(std::ostream &out, const {{name}}Trap &e) {
out << "{{table_name}}("{{#fields}}{{^first}} << ", "{{/first}}
<< {{#get_streamer}}e.{{field_name}}{{/get_streamer}}{{/fields}} << ")";
return out;
}
std::ostream &operator<<(std::ostream &out, const {{name}}Trap &e);
{{#id}}
namespace detail {

View File

@@ -14,8 +14,8 @@ def generate(opts, renderer, input):
def ret(classes):
input.classes = classes
generated = run_generation(cppgen.generate, opts, renderer)
assert set(generated) == {output_dir / "TrapClasses.h"}
generated = generated[output_dir / "TrapClasses.h"]
assert set(generated) == {output_dir / "TrapClasses"}
generated = generated[output_dir / "TrapClasses"]
assert isinstance(generated, cpp.ClassList)
return generated.classes

View File

@@ -1,5 +1,6 @@
import sys
from unittest import mock
import pathlib
import pytest
@@ -38,7 +39,7 @@ def test_constructor(pystache_renderer_cls, sut):
def test_render(pystache_renderer, sut):
data = mock.Mock()
data = mock.Mock(spec=("template",))
output = mock.Mock()
with mock.patch("builtins.open", mock.mock_open()) as output_stream:
sut.render(data, output)
@@ -54,8 +55,33 @@ def test_render(pystache_renderer, sut):
assert sut.written == {output}
def test_render_with_extensions(pystache_renderer, sut):
data = mock.Mock(spec=("template", "extensions"))
data.template = "test_template"
data.extensions = ["foo", "bar", "baz"]
output = pathlib.Path("my", "test", "file")
expected_outputs = [pathlib.Path("my", "test", p) for p in ("file.foo", "file.bar", "file.baz")]
rendered = [object() for _ in expected_outputs]
pystache_renderer.render_name.side_effect = rendered
with mock.patch("builtins.open", mock.mock_open()) as output_stream:
sut.render(data, output)
expected_templates = ["test_template_foo", "test_template_bar", "test_template_baz"]
assert pystache_renderer.mock_calls == [
mock.call.render_name(t, data, generator=generator) for t in expected_templates
]
expected_calls = []
for contents, out in zip(rendered, expected_outputs):
expected_calls.extend((
mock.call(out, 'w'),
mock.call().__enter__(),
mock.call().write(contents),
mock.call().__exit__(None, None, None),
))
assert sut.written == set(expected_outputs)
def test_written(sut):
data = [mock.Mock() for _ in range(4)]
data = [mock.Mock(spec=("template",)) for _ in range(4)]
output = [mock.Mock() for _ in data]
with mock.patch("builtins.open", mock.mock_open()) as output_stream:
for d, o in zip(data, output):
@@ -64,7 +90,7 @@ def test_written(sut):
def test_cleanup(sut):
data = [mock.Mock() for _ in range(4)]
data = [mock.Mock(spec=("template",)) for _ in range(4)]
output = [mock.Mock() for _ in data]
with mock.patch("builtins.open", mock.mock_open()) as output_stream:
for d, o in zip(data, output):

View File

@@ -15,8 +15,8 @@ def generate(opts, renderer, dbscheme_input):
dbscheme_input.entities = entities
generated = run_generation(trapgen.generate, opts, renderer)
assert set(generated) == {output_dir /
"TrapEntries.h", output_dir / "TrapTags.h"}
return generated[output_dir / "TrapEntries.h"], generated[output_dir / "TrapTags.h"]
"TrapEntries", output_dir / "TrapTags"}
return generated[output_dir / "TrapEntries"], generated[output_dir / "TrapTags"]
return ret