aboutsummaryrefslogtreecommitdiff
path: root/docs/refman/loaderbase.py
diff options
context:
space:
mode:
Diffstat (limited to 'docs/refman/loaderbase.py')
-rw-r--r--docs/refman/loaderbase.py215
1 files changed, 215 insertions, 0 deletions
diff --git a/docs/refman/loaderbase.py b/docs/refman/loaderbase.py
new file mode 100644
index 0000000..7b62713
--- /dev/null
+++ b/docs/refman/loaderbase.py
@@ -0,0 +1,215 @@
+# Copyright 2021 The Meson development team
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+
+# http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from abc import ABCMeta, abstractmethod
+from pathlib import Path
+import re
+import typing as T
+
+from .model import (
+ NamedObject,
+ FetureCheck,
+ ArgBase,
+ 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 inehritance
+ 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)
+
+ # Handloe other args inheritance
+ _T = T.TypeVar('_T', bound=T.Union[ArgBase, T.List[ArgBase]])
+ 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 metod {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 inheritence
+ 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)