Skip to content

Commit 8e07932

Browse files
committed
Swift: some restructuring of codegen
Loading of the schema and dbscheme has been moved to a separate `loaders` package for better separation of concerns.
1 parent 781aab3 commit 8e07932

19 files changed

+531
-352
lines changed

swift/codegen/generators/BUILD.bazel

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@ py_library(
66
visibility = ["//swift/codegen:__subpackages__"],
77
deps = [
88
"//swift/codegen/lib",
9-
requirement("toposort"),
9+
"//swift/codegen/loaders",
1010
],
1111
)

swift/codegen/generators/cppgen.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import inflection
1818

1919
from swift.codegen.lib import cpp, schema
20+
from swift.codegen.loaders import schemaloader
2021

2122

2223
def _get_type(t: str, add_or_none_except: typing.Optional[str] = None) -> str:
@@ -90,7 +91,7 @@ def get_classes(self):
9091

9192
def generate(opts, renderer):
9293
assert opts.cpp_output
93-
processor = Processor(schema.load_file(opts.schema))
94+
processor = Processor(schemaloader.load_file(opts.schema))
9495
out = opts.cpp_output
9596
for dir, classes in processor.get_classes().items():
9697
renderer.render(cpp.ClassList(classes, opts.schema,

swift/codegen/generators/dbschemegen.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@
1818
import inflection
1919

2020
from swift.codegen.lib import schema
21+
from swift.codegen.loaders import schemaloader
2122
from swift.codegen.lib.dbscheme import *
22-
from typing import Set, List
2323

2424
log = logging.getLogger(__name__)
2525

@@ -123,7 +123,7 @@ def generate(opts, renderer):
123123
input = opts.schema
124124
out = opts.dbscheme
125125

126-
data = schema.load_file(input)
126+
data = schemaloader.load_file(input)
127127

128128
dbscheme = Scheme(src=input.relative_to(opts.swift_dir),
129129
includes=get_includes(data, include_dir=input.parent, swift_dir=opts.swift_dir),

swift/codegen/generators/qlgen.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import inflection
3131

3232
from swift.codegen.lib import schema, ql
33+
from swift.codegen.loaders import schemaloader
3334

3435
log = logging.getLogger(__name__)
3536

@@ -297,7 +298,7 @@ def generate(opts, renderer):
297298

298299
stubs = {q for q in stub_out.rglob("*.qll")}
299300

300-
data = schema.load_file(input)
301+
data = schemaloader.load_file(input)
301302

302303
classes = {name: get_ql_class(cls) for name, cls in data.classes.items()}
303304
if not classes:

swift/codegen/generators/trapgen.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from toposort import toposort_flatten
1919

2020
from swift.codegen.lib import dbscheme, cpp
21+
from swift.codegen.loaders import dbschemeloader
2122

2223
log = logging.getLogger(__name__)
2324

@@ -73,7 +74,7 @@ def generate(opts, renderer):
7374
out = opts.cpp_output
7475

7576
traps = {pathlib.Path(): []}
76-
for e in dbscheme.iterload(opts.dbscheme):
77+
for e in dbschemeloader.iterload(opts.dbscheme):
7778
if e.is_table:
7879
traps.setdefault(e.dir, []).append(get_trap(e))
7980
elif e.is_union:

swift/codegen/lib/BUILD.bazel

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,10 @@ load("@swift_codegen_deps//:requirements.bzl", "requirement")
22

33
py_library(
44
name = "lib",
5-
srcs = glob(["**/*.py"]),
5+
srcs = glob(["*.py"]),
66
visibility = ["//swift/codegen:__subpackages__"],
77
deps = [
88
requirement("pystache"),
9-
requirement("pyyaml"),
109
requirement("inflection"),
1110
],
1211
)

swift/codegen/lib/dbscheme.py

Lines changed: 0 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -105,54 +105,3 @@ class Scheme:
105105
src: str
106106
includes: List[SchemeInclude]
107107
declarations: List[Decl]
108-
109-
110-
class Re:
111-
entity = re.compile(
112-
"(?m)"
113-
r"(?:^#keyset\[(?P<tablekeys>[\w\s,]+)\][\s\n]*)?^(?P<table>\w+)\("
114-
r"(?:\s*//dir=(?P<tabledir>\S*))?(?P<tablebody>[^\)]*)"
115-
r"\);?"
116-
"|"
117-
r"^(?P<union>@\w+)\s*=\s*(?P<unionbody>@\w+(?:\s*\|\s*@\w+)*)\s*;?"
118-
)
119-
field = re.compile(r"(?m)[\w\s]*\s(?P<field>\w+)\s*:\s*(?P<type>@?\w+)(?P<ref>\s+ref)?")
120-
key = re.compile(r"@\w+")
121-
comment = re.compile(r"(?m)(?s)/\*.*?\*/|//(?!dir=)[^\n]*$") # lookahead avoid ignoring metadata like //dir=foo
122-
123-
124-
def get_column(match):
125-
return Column(
126-
schema_name=match["field"].rstrip("_"),
127-
type=match["type"],
128-
binding=not match["ref"],
129-
)
130-
131-
132-
def get_table(match):
133-
keyset = None
134-
if match["tablekeys"]:
135-
keyset = KeySet(k.strip() for k in match["tablekeys"].split(","))
136-
return Table(
137-
name=match["table"],
138-
columns=[get_column(f) for f in Re.field.finditer(match["tablebody"])],
139-
keyset=keyset,
140-
dir=pathlib.PosixPath(match["tabledir"]) if match["tabledir"] else None,
141-
)
142-
143-
144-
def get_union(match):
145-
return Union(
146-
lhs=match["union"],
147-
rhs=(d[0] for d in Re.key.finditer(match["unionbody"])),
148-
)
149-
150-
151-
def iterload(file):
152-
with open(file) as file:
153-
data = Re.comment.sub("", file.read())
154-
for e in Re.entity.finditer(data):
155-
if e["table"]:
156-
yield get_table(e)
157-
elif e["union"]:
158-
yield get_union(e)

swift/codegen/lib/schema/schema.py renamed to swift/codegen/lib/schema.py

Lines changed: 1 addition & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,9 @@
1-
""" schema.yml format representation """
2-
import pathlib
3-
import re
4-
import types
1+
""" schema format representation """
52
import typing
63
from dataclasses import dataclass, field
74
from typing import List, Set, Union, Dict, Optional
85
from enum import Enum, auto
96
import functools
10-
import importlib.util
11-
from toposort import toposort_flatten
12-
import inflection
137

148

159
class Error(Exception):
@@ -198,125 +192,3 @@ def split_doc(doc):
198192
while trimmed and not trimmed[0]:
199193
trimmed.pop(0)
200194
return trimmed
201-
202-
203-
@dataclass
204-
class _PropertyNamer(PropertyModifier):
205-
name: str
206-
207-
def modify(self, prop: Property):
208-
prop.name = self.name.rstrip("_")
209-
210-
211-
def _get_class(cls: type) -> Class:
212-
if not isinstance(cls, type):
213-
raise Error(f"Only class definitions allowed in schema, found {cls}")
214-
# we must check that going to dbscheme names and back is preserved
215-
# In particular this will not happen if uppercase acronyms are included in the name
216-
to_underscore_and_back = inflection.camelize(inflection.underscore(cls.__name__), uppercase_first_letter=True)
217-
if cls.__name__ != to_underscore_and_back:
218-
raise Error(f"Class name must be upper camel-case, without capitalized acronyms, found {cls.__name__} "
219-
f"instead of {to_underscore_and_back}")
220-
if len({b._group for b in cls.__bases__ if hasattr(b, "_group")}) > 1:
221-
raise Error(f"Bases with mixed groups for {cls.__name__}")
222-
if any(getattr(b, "_null", False) for b in cls.__bases__):
223-
raise Error(f"Null class cannot be derived")
224-
return Class(name=cls.__name__,
225-
bases=[b.__name__ for b in cls.__bases__ if b is not object],
226-
derived={d.__name__ for d in cls.__subclasses__()},
227-
# getattr to inherit from bases
228-
group=getattr(cls, "_group", ""),
229-
# in the following we don't use `getattr` to avoid inheriting
230-
pragmas=cls.__dict__.get("_pragmas", []),
231-
ipa=cls.__dict__.get("_ipa", None),
232-
properties=[
233-
a | _PropertyNamer(n)
234-
for n, a in cls.__dict__.get("__annotations__", {}).items()
235-
],
236-
doc=split_doc(cls.__doc__),
237-
default_doc_name=cls.__dict__.get("_doc_name"),
238-
)
239-
240-
241-
def _toposort_classes_by_group(classes: typing.Dict[str, Class]) -> typing.Dict[str, Class]:
242-
groups = {}
243-
ret = {}
244-
245-
for name, cls in classes.items():
246-
groups.setdefault(cls.group, []).append(name)
247-
248-
for group, grouped in sorted(groups.items()):
249-
inheritance = {name: classes[name].bases for name in grouped}
250-
for name in toposort_flatten(inheritance):
251-
ret[name] = classes[name]
252-
253-
return ret
254-
255-
256-
def _fill_ipa_information(classes: typing.Dict[str, Class]):
257-
""" Take a dictionary where the `ipa` field is filled for all explicitly synthesized classes
258-
and update it so that all non-final classes that have only synthesized final descendants
259-
get `True` as` value for the `ipa` field
260-
"""
261-
if not classes:
262-
return
263-
264-
is_ipa: typing.Dict[str, bool] = {}
265-
266-
def fill_is_ipa(name: str):
267-
if name not in is_ipa:
268-
cls = classes[name]
269-
for d in cls.derived:
270-
fill_is_ipa(d)
271-
if cls.ipa is not None:
272-
is_ipa[name] = True
273-
elif not cls.derived:
274-
is_ipa[name] = False
275-
else:
276-
is_ipa[name] = all(is_ipa[d] for d in cls.derived)
277-
278-
root = next(iter(classes))
279-
fill_is_ipa(root)
280-
281-
for name, cls in classes.items():
282-
if cls.ipa is None and is_ipa[name]:
283-
cls.ipa = True
284-
285-
286-
def load(m: types.ModuleType) -> Schema:
287-
includes = set()
288-
classes = {}
289-
known = {"int", "string", "boolean"}
290-
known.update(n for n in m.__dict__ if not n.startswith("__"))
291-
import swift.codegen.lib.schema.defs as defs
292-
null = None
293-
for name, data in m.__dict__.items():
294-
if hasattr(defs, name):
295-
continue
296-
if name == "__includes":
297-
includes = set(data)
298-
continue
299-
if name.startswith("__"):
300-
continue
301-
cls = _get_class(data)
302-
if classes and not cls.bases:
303-
raise Error(
304-
f"Only one root class allowed, found second root {name}")
305-
cls.check_types(known)
306-
classes[name] = cls
307-
if getattr(data, "_null", False):
308-
if null is not None:
309-
raise Error(f"Null class {null} already defined, second null class {name} not allowed")
310-
null = name
311-
cls.is_null_class = True
312-
313-
_fill_ipa_information(classes)
314-
315-
return Schema(includes=includes, classes=_toposort_classes_by_group(classes), null=null)
316-
317-
318-
def load_file(path: pathlib.Path) -> Schema:
319-
spec = importlib.util.spec_from_file_location("schema", path)
320-
module = importlib.util.module_from_spec(spec)
321-
spec.loader.exec_module(module)
322-
return load(module)

swift/codegen/lib/schema/__init__.py

Lines changed: 0 additions & 1 deletion
This file was deleted.
File renamed without changes.

0 commit comments

Comments
 (0)