Skip to content

Commit effa9ee

Browse files
authored
Merge pull request #9034 from redsun82/swift-cpp-gen
Swift: add structured C++ generated classes
2 parents 265500f + 93f8b6b commit effa9ee

33 files changed

+658
-186
lines changed

swift/codegen/BUILD.bazel

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
11
load("@swift_codegen_deps//:requirements.bzl", "requirement")
22

3+
filegroup(
4+
name = "schema",
5+
srcs = ["schema.yml"],
6+
visibility = ["//swift:__subpackages__"],
7+
)
8+
9+
filegroup(
10+
name = "schema_includes",
11+
srcs = glob(["*.dbscheme"]),
12+
visibility = ["//swift:__subpackages__"],
13+
)
14+
315
py_binary(
416
name = "codegen",
517
srcs = glob(
@@ -15,6 +27,17 @@ py_binary(
1527
py_binary(
1628
name = "trapgen",
1729
srcs = ["trapgen.py"],
30+
data = ["//swift/codegen/templates:trap"],
31+
visibility = ["//swift:__subpackages__"],
32+
deps = [
33+
"//swift/codegen/lib",
34+
requirement("toposort"),
35+
],
36+
)
37+
38+
py_binary(
39+
name = "cppgen",
40+
srcs = ["cppgen.py"],
1841
data = ["//swift/codegen/templates:cpp"],
1942
visibility = ["//swift:__subpackages__"],
2043
deps = [

swift/codegen/cppgen.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import functools
2+
from typing import Dict
3+
4+
import inflection
5+
from toposort import toposort_flatten
6+
7+
from swift.codegen.lib import cpp, generator, schema
8+
9+
10+
def _get_type(t: str, trap_affix: str) -> str:
11+
if t == "string":
12+
return "std::string"
13+
if t == "boolean":
14+
return "bool"
15+
if t[0].isupper():
16+
return f"{trap_affix}Label<{t}Tag>"
17+
return t
18+
19+
20+
def _get_field(cls: schema.Class, p: schema.Property, trap_affix: str) -> cpp.Field:
21+
trap_name = None
22+
if not p.is_single:
23+
trap_name = inflection.pluralize(inflection.camelize(f"{cls.name}_{p.name}"))
24+
args = dict(
25+
name=p.name + ("_" if p.name in cpp.cpp_keywords else ""),
26+
type=_get_type(p.type, trap_affix),
27+
is_optional=p.is_optional,
28+
is_repeated=p.is_repeated,
29+
trap_name=trap_name,
30+
)
31+
args.update(cpp.get_field_override(p.name))
32+
return cpp.Field(**args)
33+
34+
35+
class Processor:
36+
def __init__(self, data: Dict[str, schema.Class], trap_affix: str):
37+
self._classmap = data
38+
self._trap_affix = trap_affix
39+
40+
@functools.lru_cache(maxsize=None)
41+
def _get_class(self, name: str) -> cpp.Class:
42+
cls = self._classmap[name]
43+
trap_name = None
44+
if not cls.derived or any(p.is_single for p in cls.properties):
45+
trap_name = inflection.pluralize(cls.name)
46+
return cpp.Class(
47+
name=name,
48+
bases=[self._get_class(b) for b in cls.bases],
49+
fields=[_get_field(cls, p, self._trap_affix) for p in cls.properties],
50+
final=not cls.derived,
51+
trap_name=trap_name,
52+
)
53+
54+
def get_classes(self):
55+
inheritance_graph = {k: cls.bases for k, cls in self._classmap.items()}
56+
return [self._get_class(cls) for cls in toposort_flatten(inheritance_graph)]
57+
58+
59+
def generate(opts, renderer):
60+
processor = Processor({cls.name: cls for cls in schema.load(opts.schema).classes}, opts.trap_affix)
61+
out = opts.cpp_output
62+
renderer.render(cpp.ClassList(processor.get_classes(), opts.cpp_namespace, opts.trap_affix,
63+
opts.cpp_include_dir), out / f"{opts.trap_affix}Classes.h")
64+
65+
66+
tags = ("cpp", "schema")
67+
68+
if __name__ == "__main__":
69+
generator.run()

swift/codegen/dbschemegen.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -38,37 +38,37 @@ def cls_to_dbscheme(cls: schema.Class):
3838
)
3939
# use property-specific tables for 1-to-many and 1-to-at-most-1 properties
4040
for f in cls.properties:
41-
if f.is_optional:
41+
if f.is_repeated:
4242
yield Table(
43-
keyset=KeySet(["id"]),
43+
keyset=KeySet(["id", "index"]),
4444
name=inflection.tableize(f"{cls.name}_{f.name}"),
4545
columns=[
4646
Column("id", type=dbtype(cls.name)),
47-
Column(f.name, dbtype(f.type)),
48-
],
47+
Column("index", type="int"),
48+
Column(inflection.singularize(f.name), dbtype(f.type)),
49+
]
4950
)
50-
elif f.is_repeated:
51+
elif f.is_optional:
5152
yield Table(
52-
keyset=KeySet(["id", "index"]),
53+
keyset=KeySet(["id"]),
5354
name=inflection.tableize(f"{cls.name}_{f.name}"),
5455
columns=[
5556
Column("id", type=dbtype(cls.name)),
56-
Column("index", type="int"),
57-
Column(inflection.singularize(f.name), dbtype(f.type)),
58-
]
57+
Column(f.name, dbtype(f.type)),
58+
],
5959
)
6060

6161

6262
def get_declarations(data: schema.Schema):
6363
return [d for cls in data.classes for d in cls_to_dbscheme(cls)]
6464

6565

66-
def get_includes(data: schema.Schema, include_dir: pathlib.Path):
66+
def get_includes(data: schema.Schema, include_dir: pathlib.Path, swift_dir: pathlib.Path):
6767
includes = []
6868
for inc in data.includes:
6969
inc = include_dir / inc
7070
with open(inc) as inclusion:
71-
includes.append(SchemeInclude(src=inc.relative_to(paths.swift_dir), data=inclusion.read()))
71+
includes.append(SchemeInclude(src=inc.relative_to(swift_dir), data=inclusion.read()))
7272
return includes
7373

7474

@@ -78,8 +78,8 @@ def generate(opts, renderer):
7878

7979
data = schema.load(input)
8080

81-
dbscheme = Scheme(src=input.relative_to(paths.swift_dir),
82-
includes=get_includes(data, include_dir=input.parent),
81+
dbscheme = Scheme(src=input.relative_to(opts.swift_dir),
82+
includes=get_includes(data, include_dir=input.parent, swift_dir=opts.swift_dir),
8383
declarations=get_declarations(data))
8484

8585
renderer.render(dbscheme, out)

swift/codegen/lib/cpp.py

Lines changed: 75 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import re
12
from dataclasses import dataclass, field
23
from typing import List, ClassVar
34

@@ -14,13 +15,35 @@
1415
"typeid", "typename", "union", "unsigned", "using", "virtual", "void", "volatile", "wchar_t", "while",
1516
"xor", "xor_eq"}
1617

18+
_field_overrides = [
19+
(re.compile(r"(start|end)_(line|column)|index|width|num_.*"), {"type": "unsigned"}),
20+
(re.compile(r"(.*)_"), lambda m: {"name": m[1]}),
21+
]
22+
23+
24+
def get_field_override(field: str):
25+
for r, o in _field_overrides:
26+
m = r.fullmatch(field)
27+
if m:
28+
return o(m) if callable(o) else o
29+
return {}
30+
1731

1832
@dataclass
1933
class Field:
2034
name: str
2135
type: str
36+
is_optional: bool = False
37+
is_repeated: bool = False
38+
trap_name: str = None
2239
first: bool = False
2340

41+
def __post_init__(self):
42+
if self.is_optional:
43+
self.type = f"std::optional<{self.type}>"
44+
if self.is_repeated:
45+
self.type = f"std::vector<{self.type}>"
46+
2447
@property
2548
def cpp_name(self):
2649
if self.name in cpp_keywords:
@@ -36,6 +59,12 @@ def get_streamer(self):
3659
else:
3760
return lambda x: x
3861

62+
@property
63+
def is_single(self):
64+
return not (self.is_optional or self.is_repeated)
65+
66+
67+
3968

4069
@dataclass
4170
class Trap:
@@ -74,13 +103,55 @@ def has_bases(self):
74103

75104
@dataclass
76105
class TrapList:
77-
template: ClassVar = 'cpp_traps'
106+
template: ClassVar = 'trap_traps'
78107

79-
traps: List[Trap] = field(default_factory=list)
108+
traps: List[Trap]
109+
namespace: str
110+
trap_affix: str
111+
include_dir: str
80112

81113

82114
@dataclass
83115
class TagList:
84-
template: ClassVar = 'cpp_tags'
116+
template: ClassVar = 'trap_tags'
117+
118+
tags: List[Tag]
119+
namespace: str
120+
121+
122+
@dataclass
123+
class ClassBase:
124+
ref: 'Class'
125+
first: bool = False
126+
127+
128+
@dataclass
129+
class Class:
130+
name: str
131+
bases: List[ClassBase] = field(default_factory=list)
132+
final: bool = False
133+
fields: List[Field] = field(default_factory=list)
134+
trap_name: str = None
135+
136+
def __post_init__(self):
137+
self.bases = [ClassBase(c) for c in sorted(self.bases, key=lambda cls: cls.name)]
138+
if self.bases:
139+
self.bases[0].first = True
140+
141+
@property
142+
def has_bases(self):
143+
return bool(self.bases)
144+
145+
@property
146+
def single_fields(self):
147+
return [f for f in self.fields if f.is_single]
148+
149+
150+
@dataclass
151+
class ClassList:
152+
template: ClassVar = "cpp_classes"
85153

86-
tags: List[Tag] = field(default_factory=list)
154+
classes: List[Class]
155+
namespace: str
156+
trap_affix: str
157+
include_dir: str

swift/codegen/lib/generator.py

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,30 @@
33
import argparse
44
import logging
55
import sys
6+
from typing import Set
67

7-
from . import options, render
8+
from . import options, render, paths
89

910

10-
def _parse(tags):
11+
def _parse(tags: Set[str]) -> argparse.Namespace:
1112
parser = argparse.ArgumentParser()
1213
for opt in options.get(tags):
1314
opt.add_to(parser)
14-
ret = parser.parse_args()
15-
log_level = logging.DEBUG if ret.verbose else logging.INFO
16-
logging.basicConfig(format="{levelname} {message}", style='{', level=log_level)
17-
return ret
15+
return parser.parse_args()
1816

1917

20-
def run(*modules):
18+
def run(*modules, **kwargs):
2119
""" run generation functions in specified in `modules`, or in current module by default
2220
"""
2321
if modules:
24-
opts = _parse({t for m in modules for t in m.tags})
22+
if kwargs:
23+
opts = argparse.Namespace(**kwargs)
24+
else:
25+
opts = _parse({t for m in modules for t in m.tags})
26+
log_level = logging.DEBUG if opts.verbose else logging.INFO
27+
logging.basicConfig(format="{levelname} {message}", style='{', level=log_level)
28+
exe_path = paths.exe_file.relative_to(opts.swift_dir)
2529
for m in modules:
26-
m.generate(opts, render.Renderer())
30+
m.generate(opts, render.Renderer(exe_path))
2731
else:
28-
run(sys.modules["__main__"])
32+
run(sys.modules["__main__"], **kwargs)

swift/codegen/lib/options.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,18 @@
1010

1111
def _init_options():
1212
Option("--verbose", "-v", action="store_true")
13+
Option("--swift-dir", type=_abspath, default=paths.swift_dir)
1314
Option("--schema", tags=["schema"], type=_abspath, default=paths.swift_dir / "codegen/schema.yml")
1415
Option("--dbscheme", tags=["dbscheme"], type=_abspath, default=paths.swift_dir / "ql/lib/swift.dbscheme")
1516
Option("--ql-output", tags=["ql"], type=_abspath, default=paths.swift_dir / "ql/lib/codeql/swift/generated")
1617
Option("--ql-stub-output", tags=["ql"], type=_abspath, default=paths.swift_dir / "ql/lib/codeql/swift/elements")
18+
Option("--ql-format", tags=["ql"], action="store_true", default=True)
19+
Option("--no-ql-format", tags=["ql"], action="store_false", dest="ql_format")
1720
Option("--codeql-binary", tags=["ql"], default="codeql")
18-
Option("--trap-output", tags=["trap"], type=_abspath, required=True)
21+
Option("--cpp-output", tags=["cpp"], type=_abspath, required=True)
22+
Option("--cpp-namespace", tags=["cpp"], default="codeql")
23+
Option("--trap-affix", tags=["cpp"], default="Trap")
24+
Option("--cpp-include-dir", tags=["cpp"], required=True)
1925

2026

2127
def _abspath(x):

swift/codegen/lib/paths.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,4 @@
1515
lib_dir = swift_dir / 'codegen' / 'lib'
1616
templates_dir = swift_dir / 'codegen' / 'templates'
1717

18-
try:
19-
exe_file = pathlib.Path(sys.argv[0]).resolve().relative_to(swift_dir)
20-
except ValueError:
21-
exe_file = pathlib.Path(sys.argv[0]).name
18+
exe_file = pathlib.Path(sys.argv[0]).resolve()

swift/codegen/lib/ql.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
@dataclass
99
class Param:
1010
param: str
11-
type: str = None
1211
first: bool = False
1312

1413

@@ -19,15 +18,11 @@ class Property:
1918
tablename: str
2019
tableparams: List[Param]
2120
plural: str = None
22-
params: List[Param] = field(default_factory=list)
2321
first: bool = False
2422
local_var: str = "x"
23+
is_optional: bool = False
2524

2625
def __post_init__(self):
27-
if self.params:
28-
self.params[0].first = True
29-
while self.local_var in (p.param for p in self.params):
30-
self.local_var += "_"
3126
assert self.tableparams
3227
if self.type_is_class:
3328
self.tableparams = [x if x != "result" else self.local_var for x in self.tableparams]
@@ -43,6 +38,10 @@ def indefinite_article(self):
4338
def type_is_class(self):
4439
return self.type[0].isupper()
4540

41+
@property
42+
def is_repeated(self):
43+
return bool(self.plural)
44+
4645

4746
@dataclass
4847
class Class:

0 commit comments

Comments
 (0)