Skip to content

Commit caaf9e7

Browse files
committed
Swift: reorganize schema lib files
1 parent b0e7dfc commit caaf9e7

File tree

2 files changed

+185
-184
lines changed

2 files changed

+185
-184
lines changed

swift/codegen/lib/schema/__init__.py

Lines changed: 1 addition & 184 deletions
Original file line numberDiff line numberDiff line change
@@ -1,184 +1 @@
1-
""" schema.yml format representation """
2-
3-
import pathlib
4-
import re
5-
from dataclasses import dataclass, field
6-
from typing import List, Set, Union, Dict, ClassVar, Optional
7-
from toposort import toposort_flatten
8-
9-
import yaml
10-
11-
12-
class Error(Exception):
13-
14-
def __str__(self):
15-
return self.args[0]
16-
17-
18-
root_class_name = "Element"
19-
20-
21-
@dataclass
22-
class Property:
23-
is_single: ClassVar = False
24-
is_optional: ClassVar = False
25-
is_repeated: ClassVar = False
26-
is_predicate: ClassVar = False
27-
28-
name: str
29-
type: str = None
30-
is_child: bool = False
31-
pragmas: List[str] = field(default_factory=list)
32-
33-
34-
@dataclass
35-
class SingleProperty(Property):
36-
is_single: ClassVar = True
37-
38-
39-
@dataclass
40-
class OptionalProperty(Property):
41-
is_optional: ClassVar = True
42-
43-
44-
@dataclass
45-
class RepeatedProperty(Property):
46-
is_repeated: ClassVar = True
47-
48-
49-
@dataclass
50-
class RepeatedOptionalProperty(Property):
51-
is_optional: ClassVar = True
52-
is_repeated: ClassVar = True
53-
54-
55-
@dataclass
56-
class PredicateProperty(Property):
57-
is_predicate: ClassVar = True
58-
59-
60-
@dataclass
61-
class IpaInfo:
62-
from_class: Optional[str] = None
63-
on_arguments: Optional[Dict[str, str]] = None
64-
65-
66-
@dataclass
67-
class Class:
68-
name: str
69-
bases: List[str] = field(default_factory=set)
70-
derived: Set[str] = field(default_factory=set)
71-
properties: List[Property] = field(default_factory=list)
72-
dir: pathlib.Path = pathlib.Path()
73-
pragmas: List[str] = field(default_factory=list)
74-
ipa: Optional[IpaInfo] = None
75-
76-
@property
77-
def final(self):
78-
return not self.derived
79-
80-
81-
@dataclass
82-
class Schema:
83-
classes: Dict[str, Class]
84-
includes: Set[str] = field(default_factory=set)
85-
86-
87-
_StrOrList = Union[str, List[str]]
88-
89-
90-
def _auto_list(data: _StrOrList) -> List[str]:
91-
if isinstance(data, list):
92-
return data
93-
return [data]
94-
95-
96-
def _parse_property(name: str, data: Union[str, Dict[str, _StrOrList]], is_child: bool = False):
97-
if isinstance(data, dict):
98-
if "type" not in data:
99-
raise Error(f"property {name} has no type")
100-
pragmas = _auto_list(data.pop("_pragma", []))
101-
type = data.pop("type")
102-
if data:
103-
raise Error(f"unknown metadata {', '.join(data)} in property {name}")
104-
else:
105-
pragmas = []
106-
type = data
107-
if is_child and type[0].islower():
108-
raise Error(f"children must have class type, got {type} for {name}")
109-
if type.endswith("?*"):
110-
return RepeatedOptionalProperty(name, type[:-2], is_child=is_child, pragmas=pragmas)
111-
elif type.endswith("*"):
112-
return RepeatedProperty(name, type[:-1], is_child=is_child, pragmas=pragmas)
113-
elif type.endswith("?"):
114-
return OptionalProperty(name, type[:-1], is_child=is_child, pragmas=pragmas)
115-
elif type == "predicate":
116-
return PredicateProperty(name, pragmas=pragmas)
117-
else:
118-
return SingleProperty(name, type, is_child=is_child, pragmas=pragmas)
119-
120-
121-
def _parse_ipa(data: Dict[str, Union[str, Dict[str, str]]]):
122-
return IpaInfo(from_class=data.get("from"),
123-
on_arguments=data.get(True)) # 'on' is parsed as boolean True in yaml
124-
125-
126-
class _DirSelector:
127-
""" Default output subdirectory selector for generated QL files, based on the `_directories` global field"""
128-
129-
def __init__(self, dir_to_patterns):
130-
self.selector = [(re.compile(p), pathlib.Path(d)) for d, p in dir_to_patterns]
131-
self.selector.append((re.compile(""), pathlib.Path()))
132-
133-
def get(self, name):
134-
return next(d for p, d in self.selector if p.search(name))
135-
136-
137-
def load(path):
138-
""" Parse the schema from the file at `path` """
139-
with open(path) as input:
140-
data = yaml.load(input, Loader=yaml.SafeLoader)
141-
grouper = _DirSelector(data.get("_directories", {}).items())
142-
classes = {root_class_name: Class(root_class_name)}
143-
classes.update((cls, Class(cls, dir=grouper.get(cls))) for cls in data if not cls.startswith("_"))
144-
for name, info in data.items():
145-
if name.startswith("_"):
146-
continue
147-
if not name[0].isupper():
148-
raise Error(f"keys in the schema file must be capitalized class names or metadata, got {name}")
149-
cls = classes[name]
150-
for k, v in info.items():
151-
if not k.startswith("_"):
152-
cls.properties.append(_parse_property(k, v))
153-
elif k == "_extends":
154-
cls.bases = _auto_list(v)
155-
for base in cls.bases:
156-
classes[base].derived.add(name)
157-
elif k == "_dir":
158-
cls.dir = pathlib.Path(v)
159-
elif k == "_children":
160-
cls.properties.extend(_parse_property(kk, vv, is_child=True) for kk, vv in v.items())
161-
elif k == "_pragma":
162-
cls.pragmas = _auto_list(v)
163-
elif k == "_synth":
164-
cls.ipa = _parse_ipa(v)
165-
else:
166-
raise Error(f"unknown metadata {k} for class {name}")
167-
if not cls.bases and cls.name != root_class_name:
168-
cls.bases = [root_class_name]
169-
classes[root_class_name].derived.add(name)
170-
171-
groups = {}
172-
173-
for name, cls in classes.items():
174-
groups.setdefault(cls.dir, []).append(name)
175-
176-
sorted_classes = {}
177-
178-
for dir in sorted(groups):
179-
group = groups[dir]
180-
inheritance = {name: classes[name].bases for name in group}
181-
for name in toposort_flatten(inheritance):
182-
sorted_classes[name] = classes[name]
183-
184-
return Schema(classes=sorted_classes, includes=set(data.get("_includes", [])))
1+
from .schema import *

swift/codegen/lib/schema/schema.py

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
""" schema.yml format representation """
2+
3+
import pathlib
4+
import re
5+
from dataclasses import dataclass, field
6+
from typing import List, Set, Union, Dict, ClassVar, Optional
7+
from toposort import toposort_flatten
8+
9+
import yaml
10+
11+
12+
class Error(Exception):
13+
14+
def __str__(self):
15+
return self.args[0]
16+
17+
18+
root_class_name = "Element"
19+
20+
21+
@dataclass
22+
class Property:
23+
is_single: ClassVar = False
24+
is_optional: ClassVar = False
25+
is_repeated: ClassVar = False
26+
is_predicate: ClassVar = False
27+
28+
name: str
29+
type: str = None
30+
is_child: bool = False
31+
pragmas: List[str] = field(default_factory=list)
32+
33+
34+
@dataclass
35+
class SingleProperty(Property):
36+
is_single: ClassVar = True
37+
38+
39+
@dataclass
40+
class OptionalProperty(Property):
41+
is_optional: ClassVar = True
42+
43+
44+
@dataclass
45+
class RepeatedProperty(Property):
46+
is_repeated: ClassVar = True
47+
48+
49+
@dataclass
50+
class RepeatedOptionalProperty(Property):
51+
is_optional: ClassVar = True
52+
is_repeated: ClassVar = True
53+
54+
55+
@dataclass
56+
class PredicateProperty(Property):
57+
is_predicate: ClassVar = True
58+
59+
60+
@dataclass
61+
class IpaInfo:
62+
from_class: Optional[str] = None
63+
on_arguments: Optional[Dict[str, str]] = None
64+
65+
66+
@dataclass
67+
class Class:
68+
name: str
69+
bases: List[str] = field(default_factory=set)
70+
derived: Set[str] = field(default_factory=set)
71+
properties: List[Property] = field(default_factory=list)
72+
dir: pathlib.Path = pathlib.Path()
73+
pragmas: List[str] = field(default_factory=list)
74+
ipa: Optional[IpaInfo] = None
75+
76+
@property
77+
def final(self):
78+
return not self.derived
79+
80+
81+
@dataclass
82+
class Schema:
83+
classes: Dict[str, Class]
84+
includes: Set[str] = field(default_factory=set)
85+
86+
87+
_StrOrList = Union[str, List[str]]
88+
89+
90+
def _auto_list(data: _StrOrList) -> List[str]:
91+
if isinstance(data, list):
92+
return data
93+
return [data]
94+
95+
96+
def _parse_property(name: str, data: Union[str, Dict[str, _StrOrList]], is_child: bool = False):
97+
if isinstance(data, dict):
98+
if "type" not in data:
99+
raise Error(f"property {name} has no type")
100+
pragmas = _auto_list(data.pop("_pragma", []))
101+
type = data.pop("type")
102+
if data:
103+
raise Error(f"unknown metadata {', '.join(data)} in property {name}")
104+
else:
105+
pragmas = []
106+
type = data
107+
if is_child and type[0].islower():
108+
raise Error(f"children must have class type, got {type} for {name}")
109+
if type.endswith("?*"):
110+
return RepeatedOptionalProperty(name, type[:-2], is_child=is_child, pragmas=pragmas)
111+
elif type.endswith("*"):
112+
return RepeatedProperty(name, type[:-1], is_child=is_child, pragmas=pragmas)
113+
elif type.endswith("?"):
114+
return OptionalProperty(name, type[:-1], is_child=is_child, pragmas=pragmas)
115+
elif type == "predicate":
116+
return PredicateProperty(name, pragmas=pragmas)
117+
else:
118+
return SingleProperty(name, type, is_child=is_child, pragmas=pragmas)
119+
120+
121+
def _parse_ipa(data: Dict[str, Union[str, Dict[str, str]]]):
122+
return IpaInfo(from_class=data.get("from"),
123+
on_arguments=data.get(True)) # 'on' is parsed as boolean True in yaml
124+
125+
126+
class _DirSelector:
127+
""" Default output subdirectory selector for generated QL files, based on the `_directories` global field"""
128+
129+
def __init__(self, dir_to_patterns):
130+
self.selector = [(re.compile(p), pathlib.Path(d)) for d, p in dir_to_patterns]
131+
self.selector.append((re.compile(""), pathlib.Path()))
132+
133+
def get(self, name):
134+
return next(d for p, d in self.selector if p.search(name))
135+
136+
137+
def load(path):
138+
""" Parse the schema from the file at `path` """
139+
with open(path) as input:
140+
data = yaml.load(input, Loader=yaml.SafeLoader)
141+
grouper = _DirSelector(data.get("_directories", {}).items())
142+
classes = {root_class_name: Class(root_class_name)}
143+
classes.update((cls, Class(cls, dir=grouper.get(cls))) for cls in data if not cls.startswith("_"))
144+
for name, info in data.items():
145+
if name.startswith("_"):
146+
continue
147+
if not name[0].isupper():
148+
raise Error(f"keys in the schema file must be capitalized class names or metadata, got {name}")
149+
cls = classes[name]
150+
for k, v in info.items():
151+
if not k.startswith("_"):
152+
cls.properties.append(_parse_property(k, v))
153+
elif k == "_extends":
154+
cls.bases = _auto_list(v)
155+
for base in cls.bases:
156+
classes[base].derived.add(name)
157+
elif k == "_dir":
158+
cls.dir = pathlib.Path(v)
159+
elif k == "_children":
160+
cls.properties.extend(_parse_property(kk, vv, is_child=True) for kk, vv in v.items())
161+
elif k == "_pragma":
162+
cls.pragmas = _auto_list(v)
163+
elif k == "_synth":
164+
cls.ipa = _parse_ipa(v)
165+
else:
166+
raise Error(f"unknown metadata {k} for class {name}")
167+
if not cls.bases and cls.name != root_class_name:
168+
cls.bases = [root_class_name]
169+
classes[root_class_name].derived.add(name)
170+
171+
groups = {}
172+
173+
for name, cls in classes.items():
174+
groups.setdefault(cls.dir, []).append(name)
175+
176+
sorted_classes = {}
177+
178+
for dir in sorted(groups):
179+
group = groups[dir]
180+
inheritance = {name: classes[name].bases for name in group}
181+
for name in toposort_flatten(inheritance):
182+
sorted_classes[name] = classes[name]
183+
184+
return Schema(classes=sorted_classes, includes=set(data.get("_includes", [])))

0 commit comments

Comments
 (0)