Files
codeql/swift/codegen/lib/schema.py
Paolo Tranquilli 64496b4c97 Swift: cleanup and some docstrings for codegen
Also added code generation and clang formatting to the pre-commit
configuration.
2022-04-14 11:27:41 +02:00

109 lines
3.0 KiB
Python

""" schema.yml format representation """
import pathlib
import re
from dataclasses import dataclass, field
from enum import Enum, auto
from typing import List, Set, Dict
import yaml
root_class_name = "Element"
class Cardinality(Enum):
""" The cardinality of a property
`ONE` is the default, `OPTIONAL` are the fields denoted by `?`, `MANY` are those denoted by `*`
"""
ONE = auto()
OPTIONAL = auto()
MANY = auto()
@dataclass
class Property:
name: str
type: str
cardinality: Cardinality = Cardinality.ONE
@property
def is_single(self):
return self.cardinality == Cardinality.ONE
@property
def is_optional(self):
return self.cardinality == Cardinality.OPTIONAL
@property
def is_repeated(self):
return self.cardinality == Cardinality.MANY
@dataclass
class Class:
name: str
bases: Set[str] = field(default_factory=set)
derived: Set[str] = field(default_factory=set)
properties: List[Property] = field(default_factory=list)
dir: pathlib.Path = pathlib.Path()
@dataclass
class Schema:
classes: Dict[str, Class]
includes: Set[str] = field(default_factory=set)
def _parse_property(name, type):
if type.endswith("*"):
cardinality = Cardinality.MANY
type = type[:-1]
elif type.endswith("?"):
cardinality = Cardinality.OPTIONAL
type = type[:-1]
else:
cardinality = Cardinality.ONE
return Property(name, type, cardinality)
class _DirSelector:
""" Default output subdirectory selector for generated QL files, based on the `_directories` global field"""
def __init__(self, dir_to_patterns):
self.selector = [(re.compile(p), pathlib.Path(d)) for d, p in dir_to_patterns]
self.selector.append((re.compile(""), pathlib.Path()))
def get(self, name):
return next(d for p, d in self.selector if p.search(name))
def load(file):
""" Parse the schema from `file` """
data = yaml.load(file, Loader=yaml.SafeLoader)
grouper = _DirSelector(data.get("_directories", {}).items())
ret = Schema(classes={cls: Class(cls, dir=grouper.get(cls)) for cls in data if not cls.startswith("_")},
includes=set(data.get("_includes", [])))
assert root_class_name not in ret.classes
ret.classes[root_class_name] = Class(root_class_name)
for name, info in data.items():
if name.startswith("_"):
continue
assert name[0].isupper()
cls = ret.classes[name]
for k, v in info.items():
if not k.startswith("_"):
cls.properties.append(_parse_property(k, v))
elif k == "_extends":
if not isinstance(v, list):
v = [v]
for base in v:
cls.bases.add(base)
ret.classes[base].derived.add(name)
elif k == "_dir":
cls.dir = pathlib.Path(v)
if not cls.bases:
cls.bases.add(root_class_name)
ret.classes[root_class_name].derived.add(name)
return ret