Source code for pyEDAA.OutputFilter.Xilinx.Common

# ==================================================================================================================== #
#               _____ ____    _        _      ___        _               _   _____ _ _ _                               #
#   _ __  _   _| ____|  _ \  / \      / \    / _ \ _   _| |_ _ __  _   _| |_|  ___(_) | |_ ___ _ __                    #
#  | '_ \| | | |  _| | | | |/ _ \    / _ \  | | | | | | | __| '_ \| | | | __| |_  | | | __/ _ \ '__|                   #
#  | |_) | |_| | |___| |_| / ___ \  / ___ \ | |_| | |_| | |_| |_) | |_| | |_|  _| | | | ||  __/ |                      #
#  | .__/ \__, |_____|____/_/   \_\/_/   \_(_)___/ \__,_|\__| .__/ \__,_|\__|_|   |_|_|\__\___|_|                      #
#  |_|    |___/                                             |_|                                                        #
# ==================================================================================================================== #
# Authors:                                                                                                             #
#   Patrick Lehmann                                                                                                    #
#                                                                                                                      #
# License:                                                                                                             #
# ==================================================================================================================== #
# Copyright 2025-2026 Electronic Design Automation Abstraction (EDA²)                                                  #
#                                                                                                                      #
# 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.                                                                                       #
#                                                                                                                      #
# SPDX-License-Identifier: Apache-2.0                                                                                  #
# ==================================================================================================================== #
#
"""Basic classes for outputs from AMD/Xilinx Vivado."""
from enum    import Flag
from pathlib import Path
from re      import Pattern, compile as re_compile
from typing  import Optional as Nullable, Self, ClassVar, Tuple, Union, Any, Iterator, Generator, Callable

from pyTooling.Decorators  import export, readonly
from pyTooling.MetaClasses import ExtendedType
from pyTooling.Common      import getFullyQualifiedName


[docs] @export class LineKind(Flag): """ Classification of a log message line. """ Unprocessed = 0 ProcessorError = 2** 0 Empty = 2** 1 Delimiter = 2** 2 Success = 2** 3 Failed = 2** 4 Verbose = 2**10 Normal = 2**11 Info = 2**12 Warning = 2**13 CriticalWarning = 2**14 Error = 2**15 Fatal = 2**16 Start = 2**20 End = 2**21 Header = 2**22 Content = 2**23 Time = 2**24 Footer = 2**25 Last = 2**29 Message = 2**30 InfoMessage = Message | Info WarningMessage = Message | Warning CriticalWarningMessage = Message | CriticalWarning ErrorMessage = Message | Error Task = 2**31 TaskStart = Task | Start TaskEnd = Task | End TaskTime = Task | Time Phase = 2**32 PhaseDelimiter = Phase | Delimiter PhaseStart = Phase | Start PhaseEnd = Phase | End PhaseTime = Phase | Time PhaseFinal = Phase | Footer SubPhase = 2**33 SubPhaseStart = SubPhase | Start SubPhaseEnd = SubPhase | End SubPhaseTime = SubPhase | Time SubSubPhase = 2**34 SubSubPhaseStart = SubSubPhase | Start SubSubPhaseEnd = SubSubPhase | End SubSubPhaseTime = SubSubPhase | Time SubSubSubPhase = 2**35 SubSubSubPhaseStart = SubSubSubPhase | Start SubSubSubPhaseEnd = SubSubSubPhase | End SubSubSubPhaseTime = SubSubSubPhase | Time NestedTask = 2**36 NestedTaskStart = NestedTask | Start NestedTaskEnd = NestedTask | End NestedPhase = 2**37 NestedPhaseStart = NestedPhase | Start NestedPhaseEnd = NestedPhase | End Section = 2**38 SectionDelimiter = Section | Delimiter SectionStart = Section | Start SectionEnd = Section | End SubSection = 2**39 SubSectionDelimiter = SubSection | Delimiter SubSectionStart = SubSection | Start SubSectionEnd = SubSection | End Paragraph = 2**40 ParagraphHeadline = Paragraph | Header Hierarchy = 2**41 HierarchyStart = Hierarchy | Start HierarchyEnd = Hierarchy | End XDC = 2**42 XDCStart = XDC | Start XDCEnd = XDC | End Table = 2**43 TableFrame = Table | Delimiter TableHeader = Table | Header TableRow = Table | Content TableFooter = Table | Footer TclCommand = 2**44 GenericTclCommand = TclCommand | 2**0 VivadoTclCommand = TclCommand | 2**1
[docs] @export class Line(metaclass=ExtendedType, slots=True): """ This class represents any line in a log file. """ _lineNumber: int _kind: LineKind _message: str _previousLine: Nullable["Line"] _nextLine: Nullable["Line"]
[docs] def __init__(self, lineNumber: int, kind: LineKind, message: str, previousLine: Nullable["Line"] = None) -> None: self._lineNumber = lineNumber self._kind = kind self._message = message self._previousLine = previousLine self._nextLine = None if previousLine is not None: previousLine._nextLine = self
@readonly def LineNumber(self) -> int: return self._lineNumber @readonly def Kind(self) -> LineKind: return self._kind @readonly def Message(self) -> str: return self._message @property def PreviousLine(self) -> "Line": return self._previousLine @PreviousLine.setter def PreviousLine(self, line: "Line") -> None: self._previousLine = line if line is not None: line._nextLine = self @readonly def NextLine(self) -> "Line": return self._nextLine def Partition(self, separator: str) -> Tuple[str, str, str]: return self._message.partition(separator) def StartsWith(self, prefix: Union[str, Tuple[str, ...]]): return self._message.startswith(prefix)
[docs] def __iter__(self) -> Generator["Line", None, None]: """ Forward iteration from the successor of this node. .. hint:: Delegates to :meth:`GetIterator` with all defaults. :returns: A generator yielding each successive :class:`Line` node. """ return self.GetIterator()
[docs] def GetIterator( self, stopPredicate: Nullable[Callable[["Line"], bool]] = None, *, reverse: bool = False, inclusive: bool = True, maxLines: Nullable[int] = None, ) -> Generator["Line", None, None]: """ Iterate consecutive lines starting from next line towards the end of the log. If the order is reversed, iterate starting at the previous line towards the beginning of the log. The iteration ends either at the bounds of the log, by specifying a stop predicate or a maximum number of lines to return. When stopped this line is usually included in the iteration, but can be excluded. :param stopPredicate: Optional, a callable receiving a :class:`Line` and returning ``True`` when iteration should stop at that line. :param reverse: Optional, reverse the iteration from previous line to the beginning of the log. :param inclusive: Optional, when ``True`` the line where ``stopPredicate`` or ``maxLines`` triggers, is included in the iteration, otherwise it's excluded. :param maxLines: Optional, maximum number of lines to yield. :returns: A generator yielding :class:`Line` in the requested direction, stopping at the log boundary, the predicate match, or the line limit — whichever comes first. :raises TypeError: When ``stopPredicate`` is not callable. :raises ValueError: When ``maxLines`` is not a positive integer. """ if stopPredicate is not None and not callable(stopPredicate): ex = TypeError("Parameter 'stopPredicate' is not a callable.") ex.add_note(f"Got type '{getFullyQualifiedName(stopPredicate)}'.") raise ex if not isinstance(reverse, bool): ex = TypeError("Parameter 'reverse' is not a boolean.") ex.add_note(f"Got type '{getFullyQualifiedName(reverse)}'.") raise ex if not isinstance(inclusive, bool): ex = TypeError("Parameter 'inclusive' is not a boolean.") ex.add_note(f"Got type '{getFullyQualifiedName(inclusive)}'.") raise ex if maxLines is not None: if not isinstance(maxLines, int): ex = TypeError("Parameter 'maxLines' is not a integer.") ex.add_note(f"Got type '{getFullyQualifiedName(maxLines)}'.") raise ex elif maxLines <= 0: ex = ValueError("Parameter 'maxLines' must be a positive integer.") ex.add_note(f"Got {maxLines!r}.") raise ex current = self._previousLine if reverse else self._nextLine if maxLines is None: if stopPredicate is None: if reverse: while current is not None: yield current current = current._previousLine else: while current is not None: yield current current = current._nextLine else: if reverse: while current is not None: if stopPredicate(current): if inclusive: yield current return yield current current = current._previousLine else: while current is not None: if stopPredicate(current): if inclusive: yield current return yield current current = current._nextLine elif stopPredicate is None: remaining = maxLines if reverse: while current is not None and remaining > 0: yield current current = current._previousLine remaining -= 1 else: while current is not None and remaining > 0: yield current current = current._nextLine remaining -= 1 else: remaining = maxLines if reverse: while current is not None and remaining > 0: if stopPredicate(current): if inclusive: yield current return yield current current = current._previousLine remaining -= 1 else: while current is not None and remaining > 0: if stopPredicate(current): if inclusive: yield current return yield current current = current._nextLine remaining -= 1
def __getitem__(self, item: slice) -> str: return self._message[item]
[docs] def __eq__(self, other: Any): return self._message == other
[docs] def __ne__(self, other: Any): return self._message != other
[docs] def __str__(self) -> str: return self._message
[docs] def __repr__(self) -> str: return f"{self._lineNumber}: {self._message}"
[docs] @export class InfoMessage(metaclass=ExtendedType, mixin=True): pass
[docs] @export class WarningMessage(metaclass=ExtendedType, mixin=True): pass
[docs] @export class CriticalWarningMessage(metaclass=ExtendedType, mixin=True): pass
[docs] @export class ErrorMessage(metaclass=ExtendedType, mixin=True): pass
[docs] @export class VivadoMessage(Line): """ This class represents an AMD/Xilinx Vivado message. The usual message format is: .. code-block:: text INFO: [Synth 8-7079] Multithreading enabled for synth_design using a maximum of 2 processes. WARNING: [Synth 8-3332] Sequential element (gen[0].Sync/FF2) is unused and will be removed from module sync_Bits_Xilinx. The following message severities are defined: * ``INFO`` * ``WARNING`` * ``CRITICAL WARNING`` * ``ERROR`` .. seealso:: :class:`VivadoInfoMessage` Representing a Vivado info message. :class:`VivadoWarningMessage` Representing a Vivado warning message. :class:`VivadoCriticalWarningMessage` Representing a Vivado critical warning message. :class:`VivadoErrorMessage` Representing a Vivado error message. """ # _MESSAGE_KIND: ClassVar[str] # _REGEXP: ClassVar[Pattern] _toolName: Nullable[str] _toolID: Nullable[int] _messageKindID: Nullable[int]
[docs] def __init__( self, lineNumber: int, kind: LineKind, message: str, toolName: Nullable[str] = None, toolID: Nullable[int] = None, messageKindID: Nullable[int] = None, previousLine: Nullable[Line] = None ) -> None: super().__init__(lineNumber, kind, message, previousLine) self._toolName = toolName self._toolID = toolID self._messageKindID = messageKindID
@readonly def ToolName(self) -> Nullable[str]: return self._toolName @readonly def ToolID(self) -> Nullable[int]: return self._toolID @readonly def MessageKindID(self) -> Nullable[int]: return self._messageKindID @classmethod def Parse(cls, lineNumber: int, kind: LineKind, rawMessage: str, previousLine: Nullable[Line] = None) -> Nullable[Self]: if (match := cls._REGEXP.match(rawMessage)) is not None: return cls(lineNumber, kind, match[4], match[1], int(match[2]), int(match[3]), previousLine) return None
[docs] def __str__(self) -> str: return f"{self._MESSAGE_KIND}: [{self._toolName} {self._toolID}-{self._messageKindID}] {self._message}"
[docs] @export class VivadoInfoMessage(VivadoMessage, InfoMessage): """ This class represents an AMD/Xilinx Vivado info message. .. rubric:: Example .. code-block:: INFO: [Common 17-83] 66-Releasing license: Synthesis """ _MESSAGE_KIND: ClassVar[str] = "INFO" _REGEXP: ClassVar[Pattern] = re_compile(r"""INFO: \[(\w+) (\d+)-(\d+)\] (.*)""") @classmethod def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[Line] = None) -> Nullable[Self]: return super().Parse(lineNumber, LineKind.InfoMessage, rawMessage, previousLine)
[docs] @export class VivadoDRCInfoMessage(VivadoMessage, InfoMessage): """ This class represents an AMD/Xilinx Vivado Design Rule Check (DRC) info message. .. rubric:: Example .. code-block:: INFO: [DRC AVAL-4] enum_USE_DPORT_FALSE_enum_DREG_ADREG_0_connects_CED_CEAD_RSTD_GND: i_system/xbip_dsp48_macro_0/U0/i_synth/i_synth_option.i_synth_model/opt_7series.i_uniwrap/i_primitive: DSP48E1 is not using the D port (USE_DPORT = FALSE). For improved power characteristics, set DREG and ADREG to '1', tie CED, CEAD, and RSTD to logic '0'. """ _MESSAGE_KIND: ClassVar[str] = "INFO" _REGEXP: ClassVar[Pattern] = re_compile(r"""INFO: \[DRC (\w+)-(\d+)\] (.*)""") _drcRuleName: str
[docs] def __init__( self, lineNumber: int, kind: LineKind, drcRuleName: str, message: str, toolName: Nullable[str] = None, toolID: Nullable[int] = None, messageKindID: Nullable[int] = None, previousLine: Nullable[Line] = None ) -> None: super().__init__(lineNumber, kind, message, toolName, toolID, messageKindID, previousLine) self._drcRuleName = drcRuleName
@readonly def DRCRuleName(self) -> str: return self._drcRuleName @classmethod def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[Line] = None) -> Nullable[Self]: if (match := cls._REGEXP.match(rawMessage)) is not None: return cls(lineNumber, LineKind.WarningMessage, match[1], match[3], toolName="DRC", toolID=None, messageKindID=int(match[2]), previousLine=previousLine) return None
[docs] def __str__(self) -> str: return f"{self._MESSAGE_KIND}: [DRC {self._drcRuleName}-{self._messageKindID}] {self._message}"
[docs] @export class VivadoIrregularInfoMessage(VivadoMessage, InfoMessage): """ This class represents an irregular AMD/Xilinx Vivado info message. .. rubric:: Example .. code-block:: INFO: [runtcl-4] Executing : report_io -file system_top_io_placed.rpt """ _MESSAGE_KIND: ClassVar[str] = "INFO" _REGEXP: ClassVar[Pattern] = re_compile(r"""INFO: \[(\w+)-(\d+)\] (.*)""") @classmethod def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[Line] = None) -> Nullable[Self]: if (match := cls._REGEXP.match(rawMessage)) is not None: return cls(lineNumber, LineKind.InfoMessage, match[3], toolName=match[1], messageKindID=int(match[2]), previousLine=previousLine) return None
[docs] def __str__(self) -> str: return f"{self._MESSAGE_KIND}: [{self._toolName}-{self._messageKindID}] {self._message}"
[docs] @export class VivadoStuntedInfoMessage(VivadoMessage, InfoMessage): """ This class represents a stunted AMD/Xilinx Vivado info message. .. rubric:: Example .. code-block:: INFO: Helper process launched with PID 29056 """ _MESSAGE_KIND: ClassVar[str] = "INFO" _REGEXP: ClassVar[Pattern] = re_compile(r"""INFO: ([^\[].*)""") @classmethod def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[Line] = None) -> Nullable[Self]: if (match := cls._REGEXP.match(rawMessage)) is not None: return cls(lineNumber, LineKind.InfoMessage, match[1], previousLine=previousLine) return None
[docs] def __str__(self) -> str: return f"{self._MESSAGE_KIND}: {self._message}"
[docs] @export class VivadoWarningMessage(VivadoMessage, WarningMessage): """ This class represents an AMD/Xilinx Vivado warning message. .. rubric:: Example .. code-block:: WARNING: [Synth 8-7080] Parallel synthesis criteria is not met """ _MESSAGE_KIND: ClassVar[str] = "WARNING" _REGEXP: ClassVar[Pattern] = re_compile(r"""WARNING: \[(\w+(?: \w+)*?) (\d+)-(\d+)\] (.*)""") @classmethod def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[Line] = None) -> Nullable[Self]: return super().Parse(lineNumber, LineKind.WarningMessage, rawMessage, previousLine=previousLine)
[docs] @export class VivadoDRCWarningMessage(VivadoMessage, WarningMessage): """ This class represents an AMD/Xilinx Vivado Design Rule Check (DRC) warning message. .. rubric:: Example .. code-block:: WARNING: [DRC PDCN-1569] LUT equation term check: Used physical LUT pin 'A1' of cell ps/path/to/cell (pin ps/path/to/cell/I0) is not included in the LUT equation: 'O6=(A6+~A6)*((A3*A2)+(A3*(~A2)*A5)+((~A3)*A4*A5)+((~A3)*(~A4)*A2)+((~A3)*(~A4)*(~A2)*A5))'. If this cell is a user instantiated LUT in the design, please remove connectivity to the pin or change the equation and/or INIT string of the LUT to prevent this issue. If the cell is inferred or IP created LUT, please regenerate the IP and/or resynthesize the design to attempt to correct the issue. """ _MESSAGE_KIND: ClassVar[str] = "WARNING" _REGEXP: ClassVar[Pattern] = re_compile(r"""WARNING: \[DRC (\w+)-(\d+)\] (.*)""") _drcRuleName: str
[docs] def __init__( self, lineNumber: int, kind: LineKind, drcRuleName: str, message: str, toolName: Nullable[str] = None, toolID: Nullable[int] = None, messageKindID: Nullable[int] = None, previousLine: Nullable[Line] = None ) -> None: super().__init__(lineNumber, kind, message, toolName, toolID, messageKindID, previousLine) self._drcRuleName = drcRuleName
@readonly def DRCRuleName(self) -> str: return self._drcRuleName @classmethod def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[Line] = None) -> Nullable[Self]: if (match := cls._REGEXP.match(rawMessage)) is not None: return cls(lineNumber, LineKind.WarningMessage, match[1], match[3], toolName="DRC", toolID=None, messageKindID=int(match[2]), previousLine=previousLine) return None
[docs] def __str__(self) -> str: return f"{self._MESSAGE_KIND}: [DRC {self._drcRuleName}-{self._messageKindID}] {self._message}"
[docs] @export class VivadoXPMWarningMessage(VivadoMessage, WarningMessage): """ This class represents an AMD/Xilinx Vivado XPM warning message. .. rubric:: Example .. code-block:: WARNING: [XPM_CDC_GRAY: TCL-1000] The source and destination clocks are the same. """ _MESSAGE_KIND: ClassVar[str] = "WARNING" _REGEXP: ClassVar[Pattern] = re_compile(r"""WARNING: \[(XPM_\w+): (\w+)-(\d+)\] (.*)""") _xpmName: str
[docs] def __init__( self, lineNumber: int, kind: LineKind, xpmName: str, message: str, toolName: Nullable[str] = None, toolID: Nullable[int] = None, messageKindID: Nullable[int] = None, previousLine: Nullable[Line] = None ) -> None: super().__init__(lineNumber, kind, message, toolName, toolID, messageKindID, previousLine) self._xpmName = xpmName
@readonly def XPMName(self) -> str: return self._xpmName @classmethod def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[Line] = None) -> Nullable[Self]: if (match := cls._REGEXP.match(rawMessage)) is not None: return cls(lineNumber, LineKind.WarningMessage, match[1], match[4], toolName=match[2], toolID=None, messageKindID=int(match[3]), previousLine=previousLine) return None
[docs] def __str__(self) -> str: return f"{self._MESSAGE_KIND}: [{self._xpmName}: {self._toolName}-{self._messageKindID}] {self._message}"
[docs] @export class VivadoStuntedWarningMessage(VivadoMessage, WarningMessage): """ This class represents a stunted AMD/Xilinx Vivado warning message. .. rubric:: Example .. code-block:: WARNING: set_property ASYNC_REG could not find object (constraint file /path/to/sync_Bits_Xilinx.xdc, line 5). """ _MESSAGE_KIND: ClassVar[str] = "WARNING" _REGEXP: ClassVar[Pattern] = re_compile(r"""WARNING: ([^\[].*)""") @classmethod def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[Line] = None) -> Nullable[Self]: if (match := cls._REGEXP.match(rawMessage)) is not None: return cls(lineNumber, LineKind.WarningMessage, match[1], previousLine=previousLine) return None
[docs] def __str__(self) -> str: return f"{self._MESSAGE_KIND}: {self._message}"
[docs] @export class VivadoCriticalWarningMessage(VivadoMessage, CriticalWarningMessage): """ This class represents an AMD/Xilinx Vivado critical warning message. .. rubric:: Example .. code-block:: CRITICAL WARNING: [Constraints 18-1056] Clock 'RefClkA_SFP_Quad' completely overrides clock 'USRCLKA_SFP[P]'. """ _MESSAGE_KIND: ClassVar[str] = "CRITICAL WARNING" _REGEXP: ClassVar[Pattern] = re_compile(r"""CRITICAL WARNING: \[(\w+) (\d+)-(\d+)\] (.*)""") @classmethod def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[Line] = None) -> Nullable[Self]: return super().Parse(lineNumber, LineKind.CriticalWarningMessage, rawMessage, previousLine)
[docs] @export class VivadoErrorMessage(VivadoMessage, ErrorMessage): """ This class represents an AMD/Xilinx Vivado error message. .. rubric:: Example .. code-block:: ERROR: [Memdata 28-96] Could not find a BMM_INFO_DESIGN property in the design. Could not generate the merged BMM file: C:/Users/username/git/design.runs/impl_1/system_top_bd.bmm """ _MESSAGE_KIND: ClassVar[str] = "ERROR" _REGEXP: ClassVar[Pattern] = re_compile(r"""ERROR: \[(\w+) (\d+)-(\d+)\] (.*)""") @classmethod def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[Line] = None) -> Nullable[Self]: return super().Parse(lineNumber, LineKind.ErrorMessage, rawMessage, previousLine)
[docs] @export class VHDLReportMessage(VivadoInfoMessage): _REGEXP: ClassVar[Pattern ] = re_compile(r"""RTL report: "(.*)" \[(.*):(\d+)\]""") _reportMessage: str _sourceFile: Path _sourceLineNumber: int
[docs] def __init__( self, lineNumber: int, rawMessage: str, toolName: str, toolID: int, messageKindID: int, reportMessage: str, sourceFile: Path, sourceLineNumber: int, previousLine: Nullable[Line] = None ) -> None: super().__init__(lineNumber, LineKind.InfoMessage, rawMessage, toolName, toolID, messageKindID, previousLine) self._reportMessage = reportMessage self._sourceFile = sourceFile self._sourceLineNumber = sourceLineNumber
@classmethod def Convert(cls, line: VivadoInfoMessage) -> Nullable[Self]: if (match := cls._REGEXP.match(line._message)) is not None: return cls(line._lineNumber, line._message, line._toolName, line._toolID, line._messageKindID, match[1], Path(match[2]), int(match[3]), previousLine=line._previousLine) return None
[docs] @export class VHDLAssertionMessage(VHDLReportMessage): _REGEXP: ClassVar[Pattern ] = re_compile(r"""RTL assertion: "(.*)" \[(.*):(\d+)\]""")
[docs] @export class TclCommand(Line): """ Represents a TCL command found in a Vivado log output. Besides the full log message (:class:`Line`), this class splits the TCL command into the command name and its arguments. """ _command: str _arguments: Tuple[str, ...]
[docs] def __init__( self, lineNumber: int, command: str, arguments: Tuple[str, ...], rawMessage: str, previousLine: Nullable[Line] = None ) -> None: super().__init__(lineNumber, LineKind.GenericTclCommand, rawMessage, previousLine) self._command = command self._arguments = arguments
@readonly def Command(self) -> str: return self._command @readonly def Arguments(self) -> Tuple[str, ...]: return self._arguments @classmethod def FromLine(cls, line: Line) -> Nullable[Self]: args = line._message.split() return cls(line._lineNumber, args[0], tuple(args[1:]), line._message, previousLine=line._previousLine)
[docs] def __str__(self) -> str: return f"{self._command} {' '.join(self._arguments)}"
[docs] @export class VivadoTclCommand(TclCommand): """ Represents a Vivado specific TCL command. """ _PREFIX: ClassVar[str] = "Command:" @classmethod def Parse(cls, lineNumber: int, rawMessage: str, previousLine: Nullable[Line] = None) -> Nullable[Self]: command = rawMessage[len(cls._PREFIX) + 1:] args = command.split() vivadoCommand = cls(lineNumber, args[0], tuple(args[1:]), rawMessage, previousLine) vivadoCommand._kind = LineKind.VivadoTclCommand return vivadoCommand
[docs] def __str__(self) -> str: return f"{self._PREFIX} {self._command} {' '.join(self._arguments)}"