def toResolvedSequence(self, resolver: "Resolver", varArgs: bool, onlyTypes: bool = False, onlyValues: bool = False) -> Sequence: """ Build the resolved sequence of those parameters. Must be called after mergeDefaults. """ sequence = SequenceBuilder() entries = self.itemsValuesOrTypes(resolver=resolver, varArgs=varArgs) if self.isNamed: entries = sorted(entries, key=lambda k: k[2].order) # Build the sequence from tools.bdl.entities.impl.fragment.type import Type for key, item, metadata in entries: if onlyTypes: Error.assertTrue(element=self.element, condition=isinstance(item, Type), message="Parameter '{}' is not a type.".format(key)) if onlyValues: Error.assertTrue(element=self.element, condition=not isinstance(item, Type), message="Parameter '{}' is not a value.".format(key)) if isinstance(item, str): element = ElementBuilder().setAttr(key="value", value=item) else: element = item.element.copy() if self.isNamed: ElementBuilder.cast(element, ElementBuilder).setAttr(key="key", value=key) sequence.pushBackElement(element) return sequence
def valueString(self) -> str: value = self.value if value is None: Error.handleFromElement(element=self.element, message="A value must be present.") assert value is not None return value
def processMacro(self, element: Element, *args: typing.Any) -> str: """ Process a macro already defined. """ sequence = element.getNestedSequence("nested") assert sequence # Build the argument list argList = [] argument = element.getNestedSequence("argument") assert argument is not None for arg in argument: Error.assertHasAttr(element=arg, attr="name") argList.append(arg.getAttr("name").value) # Sanity check Error.assertTrue( element=element, condition=len(argList) == len(args), message="Wrong number of argument(s), expected {}: {}".format( len(argList), ", ".join(argList))) for i, name in enumerate(argList): self.substitutions.register(element=element, key=name, value=args[i]) # Process the template result = self._visit(sequence=sequence) for name in argList: self.substitutions.unregister(name) return "".join(result)
def visitSubstitution(self, element: Element, result: ResultType) -> None: """ Handle substitution. """ Error.assertHasAttr(element=element, attr="name") name = element.getAttr("name").value # Check if callable and if there are arguments arguments = element.getNestedSequence("argument") if arguments is None: value = self.resolveName(name=name) else: # Resolve all arguments args = [] for arg in arguments: args.append(self.readValue(element=arg)) value = self.resolveName(name, True, *args) # Process the pipes if any. pipes = element.getNestedSequence("pipe") if pipes: for pipe in pipes: Error.assertHasAttr(element=pipe, attr="name") value = self.resolveName( pipe.getAttr("name").value, True, value) # Save the output assert isinstance( value, (int, float, str, pathlib.Path) ), "The resulting substitued value must be a number, a string or a path." self.appendSubstitution(element=element, result=result, string=str(value))
def visitInclude(self, element: Element) -> ResultType: """ Handle include. """ includePathStr = self.readValue(element=element) Error.assertTrue( element=element, condition=isinstance(includePathStr, str), message="The include path must resolve into a string, instead: '{}'." .format(includePathStr)) path = pathlib.Path(includePathStr) paths = [(base / path) for base in self.includeDirs if (base / path).is_file()] Error.assertTrue(element=element, condition=len(paths) > 0, message="No valid file '{}' within {}".format( includePathStr, str([f.as_posix() for f in self.includeDirs]))) template = bzd.template.template.Template( template=paths[0].read_text(), includeDirs=self.includeDirs, indent=self.indent) result, substitutions = template._render( substitutions=self.substitutions) # Update the current substitution object self.substitutions.update(substitutions) return result
def resolve(self, contracts: "Contracts") -> None: """ Merge a base contract into this one. The order is important, as type are inherited from the deepest base. """ elementBuilder = ElementBuilder.cast(self.element, ElementBuilder) for contract in reversed([*contracts]): # Check if the type already exists, if so attempt to merge the 2 types. existing = self.get(contract.type) if existing is not None: contractTraits = AllContracts.all.get(contract.type) assert contractTraits, "All contracts should have been validated already." try: contract = contractTraits.resolveConflict(base=contract, derived=existing) except Exception as e: Error.handleFromElement(element=self.element, message=str(e)) # Remove the existing contract if resolved into a new contract if contract is not None: self.remove(contract.type) if contract is not None: elementBuilder.pushFrontElementToNestedSequence( self.sequenceKind, contract.element) # Construct a dummy Validation to check the arguments maybeSchema = self.validationForAll if maybeSchema: Validation(schema=[maybeSchema])
def mergeDefaults(self, parameters: "Parameters") -> None: """ Merge default parameters with this. """ if not bool(parameters): return if bool(self): if self.isNamed != parameters.isNamed: Error.assertTrue(element=self.element, condition=bool(self.isNamed), message="Requires named parameters.") Error.assertTrue(element=self.element, condition=not bool(self.isNamed), message="Requires unnamed parameters.") # Merge the values order: int = 0 for name, expression, metadata in parameters.itemsMetadata(): if not self.contains(name): expression.assertTrue(condition=not expression.contracts.has("mandatory"), message="Missing mandatory parameter: '{}'.".format(name)) self.append(expression, Metadata(default=True)) # Merge the metadata self.atKeyMetadata(name).template = metadata.template self.atKeyMetadata(name).order = order order += 1
def valueNumber(self) -> float: try: return float(self.valueString) except: Error.handleFromElement(element=self.element, message="Expected a valid number.") return 0.0 # To make mypy happy
def errorSymbolConflict_(element1: Element, element2: Element) -> None: message = Error.handleFromElement( element=element1, message="Symbol name is in conflict...", throw=False) message += Error.handleFromElement(element=element2, message="...with this one.", throw=False) raise Exception(message)
def parse(self) -> SequenceParser: """ Parse the content using the provided grammar. """ index = 0 root = SequenceParser(context=self.context, parent=None, grammar=self.grammar) element = root.makeElement() checkpoints: MutableMapping[str, Grammar] = {"root": self.grammar} # Keep a reference to the content assert self.context.content content: str = self.context.content try: while index < len(content): m = None for item in self.iterateGrammar(self.defaultGrammarPre + element.getGrammar() + self.defaultGrammarPost): m = re.match(item.regexpr, content[index:]) if m: if item.fragment: fragment = item.fragment(index, index + m.end(), attrs=m.groupdict()) element.add(fragment) grammar = self.getGrammar(checkpoints=checkpoints, item=item) element = fragment.next(element, grammar) break if m is None: raise Exception("Invalid syntax.") assert m is not None index += m.end() except Exception as e: # Uncomment for debug print("**** debug ****\n", root) for item in self.iterateGrammar(self.defaultGrammarPre + element.getGrammar() + self.defaultGrammarPost): print(item.regexpr) Error.handle(context=self.context, index=index, end=index, message=str(e)) return root
def resolve(self, resolver: "Resolver") -> "EntityType": """ Resolve the types and nested templates by updating their symbol to fqn. """ fqns = resolver.resolveFQN(name=self.kind).assertValue(element=self.element, attr=self.kindAttr) ElementBuilder.cast(self.element, ElementBuilder).updateAttr(self.kindAttr, ";".join(fqns)) # Resolve the templates if available self.templates.resolve(resolver=resolver) templates = self.templates # Get and save the underlying type underlying = self.getEntityResolved(resolver=resolver) # Resolve the entity, this is needed only if the entity is defined after the one holding this type. underlying.resolveMemoized(resolver=resolver) if self.underlyingTypeAttr is not None and underlying.underlyingType is not None: ElementBuilder.cast(self.element, ElementBuilder).setAttr(self.underlyingTypeAttr, underlying.underlyingType) # Validate template arguments configTypes = underlying.getConfigTemplateTypes(resolver=resolver) if not configTypes: Error.assertTrue(element=self.element, condition=(not bool(self.templates)), attr=self.kindAttr, message="Type '{}' does not support template type arguments.".format(self.kind)) else: templates.mergeDefaults(configTypes) # Validate the template arguments values = templates.getValuesOrTypesAsDict(resolver=resolver, varArgs=False) validation = underlying.makeValidationForTemplate(resolver=resolver, parameters=configTypes) assert validation, "Cannot be empty, already checked by the condition." resultValidate = validation.validate(values, output="return") Error.assertTrue(element=self.element, attr=self.kindAttr, condition=bool(resultValidate), message=str(resultValidate)) # Save the resolved template only after the validation is completed. sequence = templates.toResolvedSequence(resolver=resolver, varArgs=False, onlyTypes=True) ElementBuilder.cast(self.element, ElementBuilder).setNestedSequence("{}_resolved".format(self.templateAttr), sequence) # Resolve contract self.contracts.resolve(underlying.contracts) return underlying
def validate(self) -> None: """ Validate the contracts. it checks the follow: - Valid contract types. """ for contract in self: contractTraits = AllContracts.allPublic.get(contract.type) if contractTraits is None: Error.handleFromElement( element=self.element, message="Contract of type '{}' is not supported.".format( contract.type))
def _render( self, substitutions: typing.Union[SubstitutionsAccessor, SubstitutionWrapper] ) -> typing.Tuple[ResultType, SubstitutionWrapper]: sequence = self.parser.parse() *_, last = sequence if last.getAttrValue("category", None) in ["if", "else", "for", "macro"]: Error.handleFromElement(element=last, message="Unterminated control block.") visitor = Visitor(substitutions, *self.args, **self.kwargs) result = visitor.visit(sequence) return result, visitor.substitutions
def elementToEntity(element: Element, extension: typing.Optional[typing.Dict[str, typing.Type[EntityType]]] = None) -> EntityType: """ Instantiate an entity from an element. """ Error.assertHasAttr(element=element, attr="category") category = element.getAttr("category").value if extension and category in extension: return extension[category](element=element) if category not in CATEGORY_TO_ENTITY: Error.handleFromElement(element=element, message="Unexpected element category: {}".format(category)) return CATEGORY_TO_ENTITY[category](element=element)
def assertTrue(self, condition: bool, message: str, throw: bool = True) -> typing.Optional[str]: return Error.assertTrue(condition=condition, element=self.element, message=message, throw=throw)
def __init__(self, element: Element, kind: str, underlyingType: typing.Optional[str] = None, template: typing.Optional[str] = None, argumentTemplate: typing.Optional[str] = None, contract: typing.Optional[str] = None, const: typing.Optional[str] = None) -> None: Error.assertHasAttr(element=element, attr=kind) self.element = element self.kindAttr = kind self.underlyingTypeAttr = underlyingType self.templateAttr = template self.argumentTemplateAttr = argumentTemplate self.contractAttr = contract self.constAttr = const
def transform(entity: Type, nested: typing.List[str], reference: bool) -> TypeConversionCallableReturn: maybeContractMin = entity.contracts.get("min") isSigned = True if maybeContractMin is None or maybeContractMin.valueNumber < 0 else False maybeContractMax = entity.contracts.get("max") bits = 32 if maybeContractMax is not None: maxValue = maybeContractMax.valueNumber if maxValue < 2**8: bits = 8 elif maxValue < 2**16: bits = 16 elif maxValue < 2**32: bits = 32 elif maxValue < 2**64: bits = 64 else: Error.handleFromElement( element=entity.element, message="Value too large, max supported is 64-bit.") if isSigned: return "bzd::Int{}Type".format(bits), nested return "bzd::UInt{}Type".format(bits), nested
def visitElement(self, element: Element, result: typing.List[str], nested: typing.Optional[typing.List[str]]) -> typing.List[str]: if element.isAttr("type"): entity = Type(element=element, kind="type", underlyingType="fqn_type", template="template_resolved" if self.isResolved else "template", argumentTemplate="argument_template_resolved" if self.isResolved else None, const="const") output = self.visitType(entity=entity, nested=[] if nested is None else nested, parameters=entity.parametersTemplateResolved) else: Error.assertHasAttr(element=element, attr="value") Error.assertTrue(element=element, condition=not nested, message="Value cannot have nested entities.") output = self.visitValue(value=element.getAttr("value").value, comment=element.getAttrValue("comment")) result.append(output) return result
def visitMacro(self, element: Element) -> None: """ Handle macro definition block. """ Error.assertHasSequence(element=element, sequence="argument") Error.assertHasSequence(element=element, sequence="nested") Error.assertHasAttr(element=element, attr="name") name = element.getAttr("name").value Error.assertTrue( element=element, attr="name", condition=(name not in self.substitutions), message= "Name conflict with macro and an already existing name: '{}'.". format(name)) # Register the macro self.substitutions.register( element=element, key=name, value=lambda *args: self.processMacro(element, *args))
def visitForBlock(self, element: Element) -> ResultType: """ Handle for loop block. """ Error.assertHasAttr(element=element, attr="value1") Error.assertHasAttr(element=element, attr="iterable") Error.assertHasSequence(element=element, sequence="nested") value1 = element.getAttr("value1").value value2 = element.getAttrValue("value2") sequence = element.getNestedSequence(kind="nested") assert sequence block: ResultType = [] # Loop through the elements iterable = self.resolveName(name=element.getAttr("iterable").value) if value2 is None: for value in iterable: self.substitutions.register(element=element, key=value1, value=value) block += self._visit(sequence=sequence) self.substitutions.unregister(value1) else: iterablePair = iterable.items() if isinstance( iterable, dict) else enumerate(iterable) for key, value in iterablePair: self.substitutions.register(element=element, key=value1, value=key) self.substitutions.register(element=element, key=value2, value=value) block += self._visit(sequence=sequence) self.substitutions.unregister(value2) self.substitutions.unregister(value1) return block
def readValue(self, element: Element) -> typing.Any: """ Read a value from an element. """ Error.assertHasAttr(element=element, attr="value") Error.assertHasAttr(element=element, attr="type") valueType = element.getAttr("type").value value = element.getAttr("value").value if valueType == "name": return self.resolveName(value) elif valueType == "number": return float(value) elif valueType == "string": return value elif valueType == "true": return True elif valueType == "false": return False Error.handleFromElement(element=element, message="Unsupported value type.")
def visitElement(self, element: Element, result: ResultType) -> ResultType: """ Go through all elements and dispatch the action. """ Error.assertHasAttr(element=element, attr="category") category = element.getAttr("category").value try: # Raw content if category == "content": Error.assertHasAttr(element=element, attr="content") string = element.getAttr("content").value result.append(string) # Substitution elif category == "substitution": self.visitSubstitution(element=element, result=result) # Comments elif category == "comment": pass # End statement elif category == "end": # Reset the else flag here to make sure nested if without else do not trigger. self.followElse = False # For loop block elif category == "for": block = self.visitForBlock(element=element) self.appendBlock(element=element, result=result, block=block) # If block elif category == "if": Error.assertHasAttr(element=element, attr="condition") conditionStr = element.getAttr("condition").value block = self.visitIfBlock(element=element, conditionStr=conditionStr) self.appendBlock(element=element, result=result, block=block) # Else block elif category == "else": block = [] if self.followElse: conditionStr = element.getAttrValue("condition", "True") # type: ignore block = self.visitIfBlock(element=element, conditionStr=conditionStr) self.appendBlock(element=element, result=result, block=block) # Macro block elif category == "macro": self.visitMacro(element=element) elif category == "include": block = self.visitInclude(element=element) self.appendBlock(element=element, result=result, block=block) else: raise Exception("Unsupported category: '{}'.".format(category)) # Propagate processed exception to the top layer except ExceptionParser as e: raise e except Exception as e: Error.handleFromElement(element=element, message=str(e)) return result
def __init__(self, element: Element) -> None: super().__init__(element, Role.Type) Error.assertHasAttr(element=element, attr="name") Error.assertHasSequence(element=element, sequence="values")
def visitElement(self, element: Element, result: typing.List[str]) -> typing.List[str]: Error.assertHasAttr(element=element, attr="name") result.append(element.getAttr("name").value) return result
def __init__(self, element: Element) -> None: super().__init__(element, Role.Meta) Error.assertHasAttr(element=element, attr="value")
def __init__(self, element: Element) -> None: Error.assertHasAttr(element=element, attr="type") self.element = element
def __init__(self, element: Element) -> None: super().__init__(element, Role.Type) Error.assertHasAttr(element=element, attr="type") self.assertTrue(condition=self.type in TYPES, message="Unsupported nested type: '{}'.".format(self.type))
def visitElement(self, element: Element, result: T) -> T: """ Main visitor, called each time a new element is discovered. """ entity = elementToEntity(element=element, extension=self.elementToEntityExtenstion) # Handle nested object if isinstance(entity, Nested): self.level += 1 for category in CATEGORIES: sequence = element.getNestedSequence(category) if sequence is not None: self.parents.append( Parent(entity=entity, category=category)) nestedResult = self._visit(sequence, result) self.entityToNested(category, entity, nestedResult, result) self.parents.pop() self.level -= 1 self.visitNestedEntities(entity, result) # Handle expression elif isinstance(entity, Expression): self.visitExpression(entity, result) # Handle method elif isinstance(entity, Method): self.visitMethod(entity, result) # Handle using elif isinstance(entity, Using): self.visitUsing(entity, result) # Handle using elif isinstance(entity, Enum): self.visitEnum(entity, result) # Handle namespace elif isinstance(entity, Namespace): Error.assertTrue( element=element, condition=(self.level == 0), message="Namespaces can only be declared at top level.") self.visitNamespace(entity, result) # Update the current namespace self.parents.append(Parent(entity=entity)) # Handle use elif isinstance(entity, Use): self.visitUse(entity, result) # Should never go here else: Error.handleFromElement(element=element, message="Unexpected entity: {}".format( type(entity))) return result
def __init__(self, element: Element) -> None: super().__init__(element, Role.Type) Error.assertHasAttr(element=element, attr="name")
def inheritanceList(self) -> typing.List[Type]: inheritanceList: typing.List[Type] = [] for element in self.element.getNestedSequenceOrEmpty("inheritance"): Error.assertHasAttr(element=element, attr="symbol") inheritanceList.append(Type(element=element, kind="symbol")) return inheritanceList