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 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 toStringFromElement(element: Element, attr: typing.Optional[str] = None, message: str = "Error") -> str: context, start, end = element.makeContext(attr=attr) return Error.toString(context=context, index=start, end=end, message=message)
def insert(self, name: typing.Optional[str], namespace: typing.List[str], path: typing.Optional[Path], element: Element, category: str, conflicts: bool = False) -> str: """ Insert a new element into the symbol map. Args: fqn: Full qualified name. path: Path associated with the element. element: Element to be registered. category: Category associated with the element, will be used for filtering. conflicts: Handle symbol FQN conflicts. """ if name is None: # These are the unnamed elements that should be progpagated to other translation units. if category in {CATEGORY_COMPOSITION, CATEGORY_GLOBAL_COMPOSITION}: fqn = FQN.makeUnique(namespace=namespace) # If not, they will be kept private. else: fqn = FQN.makeUniquePrivate() else: # Build the symbol name. fqn = FQN.fromNamespace(name=name, namespace=namespace) assert fqn is not None # Save the FQN to the element, so that it can be found during [de]serialization. # This is also used to refer to the element with the symbol tree, the top-level ones only. # In addition, it is assumed that during while resolved any element have a valid FQN. ElementBuilder.cast(element, ElementBuilder).setAttr("fqn", fqn) if self.contains(fqn=fqn): originalElement = self.getEntityResolved(fqn=fqn).assertValue( element=element).element if not conflicts or element != originalElement: SymbolMap.errorSymbolConflict_(element, originalElement) self.map[fqn] = { "c": category, "p": path.as_posix() if path is not None else "", "e": None } # Resolve context context, _, _ = element.context.resolve() element.context = context # The element is also added to the entity map, to allow further modification that will # be written when serialize is called. self.entities[fqn] = elementToEntity(element=element) return fqn
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 visitIfBlock(self, element: Element, conditionStr: str) -> ResultType: """ Handle if block. """ sequence = element.getNestedSequenceAssert(kind="nested") condition = self.evaluateCondition(conditionStr=conditionStr) self.followElse = not condition if condition: return self._visit(sequence=sequence) return []
def assertHasSequence(element: Element, sequence: str, throw: bool = True) -> typing.Optional[str]: """ Ensures an element has a specific sequence. """ if not element.isNestedSequence(sequence): return Error.handleFromElement( element=element, attr=None, message="Mising mandatory sequence '{}'.".format(sequence), throw=throw) return None
def assertHasAttr(element: Element, attr: str, throw: bool = True) -> typing.Optional[str]: """ Ensures an element has a specific attribute. """ if not element.isAttr(attr): return Error.handleFromElement( element=element, attr=None, message="Mising mandatory attribute '{}'.".format(attr), throw=throw) return None
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 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 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 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, namespace: typing.List[str]) -> None: super().__init__("namespace") for name in namespace: element = Element().setAttr("name", name) self.pushBackElementToNestedSequence(kind="name", element=element)
def metaToElement(meta: typing.Any) -> Element: return Element.fromSerialize(element=meta["e"], context=Context(path=Path(meta["p"])))
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 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