diff --git a/bspump/declarative/builder.py b/bspump/declarative/builder.py index f19f30cf..7130bc82 100644 --- a/bspump/declarative/builder.py +++ b/bspump/declarative/builder.py @@ -1,6 +1,5 @@ import logging import inspect -import asyncio import yaml @@ -20,6 +19,13 @@ ### +class IncludeNeeded(Exception): + + def __init__(self, identifier): + super().__init__() + self.Identifier = identifier + + class ExpressionBuilder(object): """ Builds an expression from configuration. @@ -40,6 +46,9 @@ def __init__(self, app, library=None, include_paths=None): self.IncludePaths = include_paths assert isinstance(include_paths, list) + # Cache for loaded includes during the parsing + self.LoadedIncludes = {} + # Register the common expression module from . import expression self.register_module(expression) @@ -80,84 +89,91 @@ async def parse(self, declaration, source_name=None): :param source_name: :return: """ - self.Identifier = None if isinstance(declaration, str) and declaration.startswith('---'): pass else: + self.Identifier = declaration declaration = await self.read(declaration) - loader = yaml.Loader(declaration) - if source_name is not None: - loader.name = source_name + while True: - # Register the constructor for each registered expression class - for name in self.ExpressionClasses: - loader.add_constructor("!{}".format(name), self._constructor) + loader = yaml.Loader(declaration) + if source_name is not None: + loader.name = source_name - loader.add_constructor("!INCLUDE", self._construct_include) - loader.add_constructor("!CONFIG", self._construct_config) + # Register the constructor for each registered expression class + for name in self.ExpressionClasses: + loader.add_constructor("!{}".format(name), self._constructor) - loader.add_constructor("tag:yaml.org,2002:ui256", self._construct_scalar) - loader.add_constructor("tag:yaml.org,2002:ui128", self._construct_scalar) - loader.add_constructor("tag:yaml.org,2002:ui64", self._construct_scalar) - loader.add_constructor("tag:yaml.org,2002:ui32", self._construct_scalar) - loader.add_constructor("tag:yaml.org,2002:ui16", self._construct_scalar) - loader.add_constructor("tag:yaml.org,2002:ui8", self._construct_scalar) + loader.add_constructor("!INCLUDE", self._construct_include) + loader.add_constructor("!CONFIG", self._construct_config) - loader.add_constructor("tag:yaml.org,2002:si256", self._construct_scalar) - loader.add_constructor("tag:yaml.org,2002:si128", self._construct_scalar) - loader.add_constructor("tag:yaml.org,2002:si64", self._construct_scalar) - loader.add_constructor("tag:yaml.org,2002:si32", self._construct_scalar) - loader.add_constructor("tag:yaml.org,2002:si16", self._construct_scalar) - loader.add_constructor("tag:yaml.org,2002:si8", self._construct_scalar) + loader.add_constructor("tag:yaml.org,2002:ui256", self._construct_scalar) + loader.add_constructor("tag:yaml.org,2002:ui128", self._construct_scalar) + loader.add_constructor("tag:yaml.org,2002:ui64", self._construct_scalar) + loader.add_constructor("tag:yaml.org,2002:ui32", self._construct_scalar) + loader.add_constructor("tag:yaml.org,2002:ui16", self._construct_scalar) + loader.add_constructor("tag:yaml.org,2002:ui8", self._construct_scalar) - loader.add_constructor("tag:yaml.org,2002:fp128", self._construct_scalar) - loader.add_constructor("tag:yaml.org,2002:fp64", self._construct_scalar) - loader.add_constructor("tag:yaml.org,2002:fp32", self._construct_scalar) - loader.add_constructor("tag:yaml.org,2002:fp16", self._construct_scalar) + loader.add_constructor("tag:yaml.org,2002:si256", self._construct_scalar) + loader.add_constructor("tag:yaml.org,2002:si128", self._construct_scalar) + loader.add_constructor("tag:yaml.org,2002:si64", self._construct_scalar) + loader.add_constructor("tag:yaml.org,2002:si32", self._construct_scalar) + loader.add_constructor("tag:yaml.org,2002:si16", self._construct_scalar) + loader.add_constructor("tag:yaml.org,2002:si8", self._construct_scalar) - loader.add_constructor("tag:yaml.org,2002:str", self._construct_scalar) + loader.add_constructor("tag:yaml.org,2002:fp128", self._construct_scalar) + loader.add_constructor("tag:yaml.org,2002:fp64", self._construct_scalar) + loader.add_constructor("tag:yaml.org,2002:fp32", self._construct_scalar) + loader.add_constructor("tag:yaml.org,2002:fp16", self._construct_scalar) - expressions = [] - try: - # Build syntax trees for each expression - while loader.check_data(): - expression = loader.get_data() - expression = await self._load_includes(expression) + loader.add_constructor("tag:yaml.org,2002:str", self._construct_scalar) + + try: + expressions = [] - # Run initialize for the expression and any instance inside - for parent, key, obj in self._walk(expression): + # Build syntax trees for each expression + while loader.check_data(): + expression = loader.get_data() - if isinstance(obj, str) and obj.startswith(""): - obj = (await self.parse(obj[9:], ""))[0] - setattr(parent, key, obj) + # Run initialize for the expression and any instance inside + for parent, key, obj in self._walk(expression): - if not isinstance(obj, Expression): - continue + if not isinstance(obj, Expression): + continue - if source_name != "": + if source_name != "": - if isinstance(obj, SELF): - # Implement a self-reference or Y-Combinator - obj.initialize(expression) - else: - obj.initialize() + if isinstance(obj, SELF): + # Implement a self-reference or Y-Combinator + obj.initialize(expression) + else: + obj.initialize() - expressions.append(expression) + expressions.append(expression) - except yaml.scanner.ScannerError as e: - raise DeclarationError("Syntax error in declaration: {}".format(e)) + except yaml.scanner.ScannerError as e: + raise DeclarationError("Syntax error in declaration: {}".format(e)) - except yaml.constructor.ConstructorError as e: - raise DeclarationError("Unknown declarative expression: {}".format(e)) + except yaml.constructor.ConstructorError as e: + raise DeclarationError("Unknown declarative expression: {}".format(e)) - finally: - loader.dispose() + except IncludeNeeded as e: + # If include is needed, load its declaration to the loaded include cache + include_declaration = await self.read(e.Identifier) + parsed_declaration = await self.parse(include_declaration, "") - return expressions + # Include can be only one expression + self.LoadedIncludes[e.Identifier] = parsed_declaration[0] + continue + + finally: + loader.dispose() + + return expressions async def parse_ext(self, declaration, source_name=None): @@ -223,40 +239,16 @@ def _walk(self, expression): raise NotImplementedError("Walk not implemented for '{}'.".format(expression)) - async def _load_includes(self, expression): - - if isinstance(expression, Expression): - return expression - - if isinstance(expression, str): - - if expression.startswith(""): - expression = (await self.parse(expression[9:], ""))[0] - - return expression - - if isinstance(expression, dict): - - for _key, _value in expression.items(): - expression[_key] = await self._load_includes(_value) - - return expression - - if isinstance(expression, (list, set)): - - for _n, _value in enumerate(expression): - expression[_n] = await self._load_includes(_value) - - return expression - - return expression - - def _construct_include(self, loader: yaml.Loader, node: yaml.Node): - """Include will be done later using await.""" + """Include file referenced at node.""" identifier = loader.construct_scalar(node) - return "{}".format(identifier) + + try: + return self.LoadedIncludes[identifier] + + except KeyError: + raise IncludeNeeded(identifier) def _construct_config(self, loader: yaml.Loader, node: yaml.Node): @@ -294,6 +286,9 @@ def _constructor(self, loader, node): obj.Node = node return obj + except IncludeNeeded as e: + raise e + except TypeError as e: raise DeclarationError("Type error {}\n{}\n".format(e, node.start_mark))