Source code for pyaiml21.parser.logger

"""Logger to keep track of syntax errors."""
from pyaiml21.aiml import AIMLVersion
from pyaiml21.ast import Node
from typing import Set, Tuple, Optional

# within the logger, we represent the location as file, line and col, tag
_Location = Tuple[Optional[str], Optional[int], Optional[int], Optional[str]]
# reported message is a location and description
_ReportedMsg = Tuple[_Location, str]


def _extract_location(node: Node) -> _Location:
    """Extract location from the node."""
    return (node.filename, node.line, node.col, node.tag)


def _format_location(loc: _Location) -> str:
    """Return the formatted location used for reporting errors."""
    f, l, c, tag = loc
    file = f or "{unknown file/online}"
    lineno = "" if l is None else f"Line: {l}"
    colno = "" if c is None else f"Column: {c}"
    location = file
    if lineno:
        location += "\t" + lineno
    if colno:
        location += "\t" + colno
    if tag:
        return f"[{location}] <{tag}>:"
    return f"[{location}] TEXT:"


[docs]class Logger: """ Logger instance is used to report error and warnings during parsing. The logger offers 3 levels of error messages: * weak warning - warnings that should be presented to the user only in the strict mode * warning - messages about elements that are either ignored or their correct representation can be deduced * errors - unrepairable errors in the representation Care should be taken that error-nous aiml constructs are not to be processed anymore as they contain in-interpretable code. Warnings are generally treated as if the corresponding element was not present or was modified accordingly. """ def __init__(self): """Create an empty logger.""" self._weak_warnings: Set[_ReportedMsg] = set() self._warnings: Set[_ReportedMsg] = set() self._errors: Set[_ReportedMsg] = set()
[docs] def has_warnings(self) -> bool: """Return true if any warnings were found during parsing.""" return len(self._warnings) > 0
[docs] def has_errors(self) -> bool: """Return true if any errors were discovered during parsing.""" return len(self._errors) > 0
[docs] def weak_warning(self, node: Node, msg: str) -> None: """Report a weak warning.""" loc = _extract_location(node) self._weak_warnings.add((loc, msg))
[docs] def warning(self, node: Node, msg: str) -> None: """Report a warning about `node` as text `msg`.""" loc = _extract_location(node) self._warnings.add((loc, msg))
[docs] def error(self, node: Node, msg: str) -> None: """Report an error about `node` as text `msg`.""" loc = _extract_location(node) self._errors.add((loc, msg))
[docs] def report(self, with_stats: bool = False, strict: bool = False) -> str: """ Generate a string report about all errors and warnings found. :param with_stats: True if summary of errors and warnings should be included :param strict: if true, include also weak warnings in the summary :return: string with formatted report """ rep = [] def format_noun(noun, count): return f"{count} {noun}{'s' if count != 1 else ''}" def process(list_: Set[_ReportedMsg], name): if list_: rep.append(f"{name}:") for loc, msg in sorted(list_): loc_s = _format_location(loc) rep.append(f"\t{loc_s}: {msg}") rep.append("") # add a blank line process(self._errors, "Errors") warnings = set(self._warnings) if strict: warnings.update(self._weak_warnings) process(warnings, "Warnings") if with_stats: warns = format_noun("warning", len(warnings)) errs = format_noun("error", len(self._errors)) rep.append(f"Summary: {errs}, {warns}") return "\n".join(rep)
# # specialized methods #
[docs] def warn_unexpected_attr(self, node: Node, attr: str): """Issue a warning about unexpected attribute.""" self.warning(node, f"Unexpected attribute: \"{attr}\"")
[docs] def error_missing_attr(self, node: Node, attr: str): """Issue an error about missing required attribute.""" self.error(node, f"Missing attribute: \"{attr}\"")
[docs] def error_xml_syntax(self, msg: str, file: Optional[str], line: Optional[int] = None, col: Optional[int] = None): """Issue an error when XML is malformed.""" dummy_node = Node.Value("", filename=file, line=line, col=col) self.error(dummy_node, f"XML parsing error: \"{msg}\"")
[docs] def warn_xml_syntax(self, msg: str, file: Optional[str], line: Optional[int] = None, col: Optional[int] = None): """Issue a warning during XML parsing.""" dummy_node = Node.Value("", filename=file, line=line, col=col) self.warning(dummy_node, f"XML parsing warning: \"{msg}\"")
[docs] def warn_unexpected_element(self, node: Node): """Issue a warning about unexpected element.""" # we allow for spaces to separate any elements if node.text == " ": return elem = node.tag or f"\"{node.text}\"" self.warning(node, f"Unexpected element: {elem}")
[docs] def error_duplicate_element(self, node: Node): """Issue an error about duplicated element.""" self.error(node, f"Found duplicate element: {node.tag}")
[docs] def error_unexpected_element(self, node: Node): """Issue an error about unexpected element.""" # we allow for spaces to separate any elements if node.text == " ": return self.error(node, f"Unexpected element: {node.tag or f'{node.text}'}")
[docs] def warn_not_defined(self, node: Node, def_version: AIMLVersion): """Warn if `node.tag` is not defined in given version.""" self.warning(node, f"not defined in {node.aiml_version}, " f"first defined in {def_version.value} " f"- using forward compatible mode")
[docs] def warn_deprecated(self, node: Node, last_version: AIMLVersion): """Issue a deprecation warning about `node`.""" self.warning(node, f"deprecated in {node.aiml_version}, " f"last defined in {last_version.value}")