|
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
# Copyright 2021 The Meson development team
|
|
|
|
|
|
|
|
from abc import ABCMeta, abstractmethod
|
|
|
|
from pathlib import Path
|
|
|
|
import re
|
|
|
|
import typing as T
|
|
|
|
|
|
|
|
from .model import (
|
|
|
|
NamedObject,
|
|
|
|
FetureCheck,
|
|
|
|
ArgBase,
|
|
|
|
PosArg,
|
|
|
|
DataTypeInfo,
|
|
|
|
Type,
|
|
|
|
Function,
|
|
|
|
Method,
|
|
|
|
Object,
|
|
|
|
ObjectType,
|
|
|
|
ReferenceManual,
|
|
|
|
)
|
|
|
|
|
|
|
|
from mesonbuild import mlog
|
|
|
|
|
|
|
|
class _Resolver:
|
|
|
|
def __init__(self) -> None:
|
|
|
|
self.type_map: T.Dict[str, Object] = {}
|
|
|
|
self.func_map: T.Dict[str, T.Union[Function, Method]] = {}
|
|
|
|
self.processed_funcs: T.Set[str] = set()
|
|
|
|
|
|
|
|
def _validate_named_object(self, obj: NamedObject) -> None:
|
|
|
|
name_regex = re.compile(r'[a-zA-Z0-9_]+')
|
|
|
|
obj.name = obj.name.strip()
|
|
|
|
obj.description = obj.description.strip()
|
|
|
|
assert obj.name and obj.description, 'Both name and description must be set'
|
|
|
|
assert obj.name.islower(), f'Object names must be lower case ({obj.name})'
|
|
|
|
assert name_regex.match(obj.name) or obj.name == '[index]', f'Invalid name {obj.name}'
|
|
|
|
|
|
|
|
def _validate_feature_check(self, obj: FetureCheck) -> None:
|
|
|
|
meson_version_reg = re.compile(r'[0-9]+\.[0-9]+\.[0-9]+')
|
|
|
|
obj.since = obj.since.strip()
|
|
|
|
obj.deprecated = obj.deprecated.strip()
|
|
|
|
if obj.since:
|
|
|
|
assert meson_version_reg.match(obj.since)
|
|
|
|
if obj.deprecated:
|
|
|
|
assert meson_version_reg.match(obj.deprecated)
|
|
|
|
|
|
|
|
def _resolve_type(self, raw: str) -> Type:
|
|
|
|
typ = Type(raw)
|
|
|
|
# We can't use `types = raw.split('|')`, because of `list[str | env]`
|
|
|
|
types: T.List[str] = ['']
|
|
|
|
stack = 0
|
|
|
|
for c in raw:
|
|
|
|
if stack == 0 and c == '|':
|
|
|
|
types += ['']
|
|
|
|
continue
|
|
|
|
if c == '[':
|
|
|
|
stack += 1
|
|
|
|
if c == ']':
|
|
|
|
stack -= 1
|
|
|
|
types[-1] += c
|
|
|
|
types = [x.strip() for x in types]
|
|
|
|
for t in types:
|
|
|
|
t = t.strip()
|
|
|
|
idx = t.find('[')
|
|
|
|
base_type = t
|
|
|
|
held_type = None
|
|
|
|
if idx > 0:
|
|
|
|
base_type = t[:idx]
|
|
|
|
held_type = self._resolve_type(t[idx+1:-1])
|
|
|
|
assert base_type in self.type_map, f'No known object {t}'
|
|
|
|
obj = self.type_map[base_type]
|
|
|
|
typ.resolved += [DataTypeInfo(obj, held_type)]
|
|
|
|
return typ
|
|
|
|
|
|
|
|
def _validate_func(self, func: T.Union[Function, Method]) -> None:
|
|
|
|
# Always run basic checks, since they also slightly post-process (strip) some strings
|
|
|
|
self._validate_named_object(func)
|
|
|
|
self._validate_feature_check(func)
|
|
|
|
|
|
|
|
func_id = f'{func.obj.name}.{func.name}' if isinstance(func, Method) else func.name
|
|
|
|
if func_id in self.processed_funcs:
|
|
|
|
return
|
|
|
|
|
|
|
|
func.returns = self._resolve_type(func.returns.raw)
|
|
|
|
|
|
|
|
all_args: T.List[ArgBase] = []
|
|
|
|
all_args += func.posargs
|
|
|
|
all_args += func.optargs
|
|
|
|
all_args += func.kwargs.values()
|
|
|
|
all_args += [func.varargs] if func.varargs else []
|
|
|
|
|
|
|
|
for arg in all_args:
|
|
|
|
arg.type = self._resolve_type(arg.type.raw)
|
|
|
|
|
|
|
|
# Handle returned_by
|
|
|
|
for obj in func.returns.resolved:
|
|
|
|
obj.data_type.returned_by += [func]
|
|
|
|
|
|
|
|
# Handle kwargs inheritance
|
|
|
|
for base_name in func.kwargs_inherit:
|
|
|
|
base_name = base_name.strip()
|
|
|
|
assert base_name in self.func_map, f'Unknown base function `{base_name}` for {func.name}'
|
|
|
|
base = self.func_map[base_name]
|
|
|
|
if base_name not in self.processed_funcs:
|
|
|
|
self._validate_func(base)
|
|
|
|
|
|
|
|
curr_keys = set(func.kwargs.keys())
|
|
|
|
base_keys = set(base.kwargs.keys())
|
|
|
|
|
|
|
|
# Calculate the missing kwargs from the current set
|
|
|
|
missing = {k: v for k, v in base.kwargs.items() if k in base_keys - curr_keys}
|
|
|
|
func.kwargs.update(missing)
|
|
|
|
|
|
|
|
# Handle other args inheritance
|
|
|
|
_T = T.TypeVar('_T', bound=T.Union[ArgBase, T.List[PosArg]])
|
|
|
|
def resolve_inherit(name: str, curr: _T, resolver: T.Callable[[Function], _T]) -> _T:
|
|
|
|
if name and not curr:
|
|
|
|
name = name.strip()
|
|
|
|
assert name in self.func_map, f'Unknown base function `{name}` for {func.name}'
|
|
|
|
if name not in self.processed_funcs:
|
|
|
|
self._validate_func(self.func_map[name])
|
|
|
|
ref_args = resolver(self.func_map[name])
|
|
|
|
assert ref_args is not None, f'Inherited function `{name}` does not have inherited args set'
|
|
|
|
return ref_args
|
|
|
|
return curr
|
|
|
|
|
|
|
|
func.posargs = resolve_inherit(func.posargs_inherit, func.posargs, lambda x: x.posargs)
|
|
|
|
func.optargs = resolve_inherit(func.optargs_inherit, func.optargs, lambda x: x.optargs)
|
|
|
|
func.varargs = resolve_inherit(func.varargs_inherit, func.varargs, lambda x: x.varargs)
|
|
|
|
|
|
|
|
self.processed_funcs.add(func_id)
|
|
|
|
|
|
|
|
def validate_and_resolve(self, manual: ReferenceManual) -> ReferenceManual:
|
|
|
|
mlog.log('Validating loaded manual...')
|
|
|
|
|
|
|
|
# build type map and func map for methods
|
|
|
|
for obj in manual.objects:
|
|
|
|
assert obj.name not in self.type_map, f'Duplicate object name {obj.name}'
|
|
|
|
self.type_map[obj.name] = obj
|
|
|
|
for m in obj.methods:
|
|
|
|
mid = f'{obj.name}.{m.name}'
|
|
|
|
assert mid not in self.type_map, f'Duplicate method {mid}'
|
|
|
|
self.func_map[mid] = m
|
|
|
|
|
|
|
|
# Build func map for functions
|
|
|
|
for func in manual.functions:
|
|
|
|
assert func.name not in [*self.func_map.keys()], f'Duplicate function {func.name}'
|
|
|
|
self.func_map[func.name] = func
|
|
|
|
|
|
|
|
mlog.log('Validating functions...')
|
|
|
|
for func in manual.functions:
|
|
|
|
mlog.log(' -- validating', mlog.bold(func.name))
|
|
|
|
self._validate_func(func)
|
|
|
|
|
|
|
|
mlog.log('Validating objects...')
|
|
|
|
for obj in manual.objects:
|
|
|
|
mlog.log(' -- validating', mlog.bold(obj.name))
|
|
|
|
self._validate_named_object(obj)
|
|
|
|
self._validate_feature_check(obj)
|
|
|
|
# Resolve and validate inheritance
|
|
|
|
if obj.extends:
|
|
|
|
assert obj.extends in self.type_map, f'Unknown extends object {obj.extends} in {obj.name}'
|
|
|
|
obj.extends_obj = self.type_map[obj.extends]
|
|
|
|
obj.extends_obj.extended_by += [obj]
|
|
|
|
# Only returned objects can be associated with module
|
|
|
|
if obj.obj_type is not ObjectType.RETURNED:
|
|
|
|
assert obj.defined_by_module is None
|
|
|
|
for m in obj.methods:
|
|
|
|
assert m.obj is obj
|
|
|
|
self._validate_func(m)
|
|
|
|
|
|
|
|
# Resolve inherited methods
|
|
|
|
for obj in manual.objects:
|
|
|
|
inherited_methods = obj.inherited_methods
|
|
|
|
curr = obj.extends_obj
|
|
|
|
while curr is not None:
|
|
|
|
inherited_methods += curr.methods
|
|
|
|
curr = curr.extends_obj
|
|
|
|
return manual
|
|
|
|
|
|
|
|
class LoaderBase(metaclass=ABCMeta):
|
|
|
|
def __init__(self) -> None:
|
|
|
|
self._input_files: T.List[Path] = []
|
|
|
|
|
|
|
|
@property
|
|
|
|
def input_files(self) -> T.List[Path]:
|
|
|
|
return list(self._input_files)
|
|
|
|
|
|
|
|
def read_file(self, f: Path) -> str:
|
|
|
|
assert f.exists()
|
|
|
|
assert f.is_file()
|
|
|
|
self._input_files += [f.resolve()]
|
|
|
|
return f.read_text(encoding='utf-8')
|
|
|
|
|
|
|
|
@abstractmethod
|
|
|
|
def load_impl(self) -> ReferenceManual:
|
|
|
|
pass
|
|
|
|
|
|
|
|
def load(self) -> ReferenceManual:
|
|
|
|
self._input_files = [] # Reset input files
|
|
|
|
manual = self.load_impl()
|
|
|
|
resolver = _Resolver()
|
|
|
|
with mlog.nested():
|
|
|
|
return resolver.validate_and_resolve(manual)
|