blob: a2710fb840fe56a683484afbfbe184bdcc31d260 [file]
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you 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.
from __future__ import annotations
from antlr4.error.ErrorListener import ErrorListener
class ThrowingErrorListener(ErrorListener):
"""ANTLR error listener that throws exceptions on syntax errors."""
def __init__(self, filename: str = "<input>") -> None:
super().__init__()
self.filename = filename
def syntaxError(self, recognizer: object, _: object, line: int, column: int, msg: str, e: object) -> None:
code_line = ""
try:
if hasattr(recognizer, 'inputStream'):
# Lexer
input_stream = recognizer.inputStream
else:
# Parser
input_stream = recognizer.getInputStream().tokenSource.inputStream
if input_stream is not None:
code_line = input_stream.strdata.splitlines()[line - 1]
except Exception:
pass
raise Hrw4uSyntaxError(self.filename, line, column, msg, code_line)
class Hrw4uSyntaxError(Exception):
"""Formatted syntax error with source context and Python 3.11+ exception notes."""
def __init__(self, filename: str, line: int, column: int, message: str, source_line: str) -> None:
super().__init__(self._format_error(filename, line, column, message, source_line))
self.filename = filename
self.line = line
self.column = column
self.source_line = source_line
def add_context_note(self, context: str) -> None:
"""Add contextual information using Python 3.11+ exception notes."""
self.add_note(f"Context: {context}")
def add_resolution_hint(self, hint: str) -> None:
"""Add resolution hint using Python 3.11+ exception notes."""
self.add_note(f"Hint: {hint}")
def _format_error(self, filename: str, line: int, col: int, message: str, source_line: str) -> str:
error = f"{filename}:{line}:{col}: error: {message}"
lineno = f"{line:4d}"
code_line = f"{lineno} | {source_line}"
pointer_line = f"{' ' * 4} | {' ' * col}^"
return f"{error}\n{code_line}\n{pointer_line}"
class SymbolResolutionError(Exception):
def __init__(self, name: str, message: str | None = None) -> None:
self.name = name
super().__init__(message or f"Unrecognized symbol: '{name}'")
def add_symbol_suggestion(self, suggestions: list[str]) -> None:
if suggestions:
self.add_note(f" | Did you mean: {suggestions[0]}?")
def hrw4u_error(filename: str, ctx: object, exc: Exception) -> Hrw4uSyntaxError:
"""Convert exceptions to formatted syntax errors with source context."""
if isinstance(exc, Hrw4uSyntaxError):
return exc
if ctx is None:
error = Hrw4uSyntaxError(filename, 0, 0, str(exc), "")
else:
try:
input_stream = ctx.start.getInputStream()
source_line = input_stream.strdata.splitlines()[ctx.start.line - 1]
except Exception:
source_line = ""
error = Hrw4uSyntaxError(filename, ctx.start.line, ctx.start.column, str(exc), source_line)
if hasattr(exc, '__notes__') and exc.__notes__:
for note in exc.__notes__:
error.add_note(note)
return error
class ErrorCollector:
"""Collects multiple syntax errors for comprehensive error reporting."""
def __init__(self) -> None:
"""Initialize an empty error collector."""
self.errors: list[Hrw4uSyntaxError] = []
def add_error(self, error: Hrw4uSyntaxError) -> None:
"""
Add a syntax error to the collection.
"""
self.errors.append(error)
def has_errors(self) -> bool:
"""
Check if any errors have been collected.
"""
return bool(self.errors)
def get_error_summary(self) -> str:
if not self.errors:
return "No errors found."
count = len(self.errors)
lines = [f"Found {count} error{'s' if count > 1 else ''}:"]
for error in self.errors:
lines.append(str(error))
if hasattr(error, '__notes__') and error.__notes__:
lines.extend(error.__notes__)
return "\n".join(lines)
class CollectingErrorListener(ErrorListener):
"""ANTLR error listener that collects syntax errors for tolerant parsing."""
def __init__(self, filename: str = "<input>", error_collector: ErrorCollector | None = None) -> None:
super().__init__()
self.filename = filename
self.error_collector = error_collector or ErrorCollector()
def syntaxError(self, recognizer: object, _: object, line: int, column: int, msg: str, e: object) -> None:
code_line = ""
try:
if hasattr(recognizer, 'inputStream'):
input_stream = recognizer.inputStream
else:
input_stream = recognizer.getInputStream().tokenSource.inputStream
if input_stream is not None:
code_line = input_stream.strdata.splitlines()[line - 1]
except Exception:
pass
error = Hrw4uSyntaxError(self.filename, line, column, msg, code_line)
self.error_collector.add_error(error)