Commit
277df18d7df9 ("xdrgen: Improve parse error reporting") added
clean, compiler-style error messages for syntax errors detected during
parsing. However, semantic errors discovered during AST transformation
still produce verbose Python stack traces.
When an XDR specification references an undefined type, the transformer
raises a VisitError wrapping a KeyError. Before this change:
Traceback (most recent call last):
File ".../lark/visitors.py", line 124, in _call_userfunc
return f(children)
...
KeyError: 'fsh4_mode'
...
lark.exceptions.VisitError: Error trying to process rule "basic":
'fsh4_mode'
After this change:
file.x:156:2: semantic error
Undefined type 'fsh4_mode'
fsh4_mode mode;
^
The new handle_transform_error() function extracts position information
from the Lark tree node metadata and formats the error consistently with
parse error messages.
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
from argparse import Namespace
from lark import logger
+from lark.exceptions import VisitError
from generators.constant import XdrConstantGenerator
from generators.enum import XdrEnumGenerator
from xdr_ast import _XdrTypedef, _XdrStruct, _XdrUnion
from xdr_parse import xdr_parser, set_xdr_annotate
from xdr_parse import make_error_handler, XdrParseError
+from xdr_parse import handle_transform_error
logger.setLevel(logging.INFO)
)
except XdrParseError:
return 1
- ast = transform_parse_tree(parse_tree)
+ try:
+ ast = transform_parse_tree(parse_tree)
+ except VisitError as e:
+ handle_transform_error(e, source, args.filename)
+ return 1
gen = XdrHeaderTopGenerator(args.language, args.peer)
gen.emit_declaration(args.filename, ast)
from argparse import Namespace
from lark import logger
+from lark.exceptions import VisitError
from generators.constant import XdrConstantGenerator
from generators.enum import XdrEnumGenerator
from xdr_ast import _XdrTypedef, _XdrStruct, _XdrUnion
from xdr_parse import xdr_parser, set_xdr_annotate
from xdr_parse import make_error_handler, XdrParseError
+from xdr_parse import handle_transform_error
logger.setLevel(logging.INFO)
)
except XdrParseError:
return 1
- ast = transform_parse_tree(parse_tree)
+ try:
+ ast = transform_parse_tree(parse_tree)
+ except VisitError as e:
+ handle_transform_error(e, source, args.filename)
+ return 1
gen = XdrHeaderTopGenerator(args.language, args.peer)
gen.emit_definition(args.filename, ast)
from argparse import Namespace
from lark import logger
+from lark.exceptions import VisitError
from xdr_parse import xdr_parser, make_error_handler, XdrParseError
+from xdr_parse import handle_transform_error
from xdr_ast import transform_parse_tree
logger.setLevel(logging.DEBUG)
)
except XdrParseError:
return 1
- transform_parse_tree(parse_tree)
+ try:
+ transform_parse_tree(parse_tree)
+ except VisitError as e:
+ handle_transform_error(e, source, args.filename)
+ return 1
return 0
from argparse import Namespace
from lark import logger
+from lark.exceptions import VisitError
from generators.source_top import XdrSourceTopGenerator
from generators.enum import XdrEnumGenerator
from xdr_parse import xdr_parser, set_xdr_annotate
from xdr_parse import make_error_handler, XdrParseError
+from xdr_parse import handle_transform_error
logger.setLevel(logging.INFO)
)
except XdrParseError:
return 1
- ast = transform_parse_tree(parse_tree)
+ try:
+ ast = transform_parse_tree(parse_tree)
+ except VisitError as e:
+ handle_transform_error(e, source, args.filename)
+ return 1
match args.peer:
case "server":
generate_server_source(args.filename, ast, args.language)
from typing import Callable
from lark import Lark
-from lark.exceptions import UnexpectedInput, UnexpectedToken
+from lark.exceptions import UnexpectedInput, UnexpectedToken, VisitError
# Set to True to emit annotation comments in generated source
return handle_parse_error
+def handle_transform_error(e: VisitError, source: str, filename: str) -> None:
+ """Report a transform error with context.
+
+ Args:
+ e: The VisitError from Lark's transformer
+ source: The XDR source text being parsed
+ filename: The name of the file being parsed
+ """
+ lines = source.splitlines()
+
+ # Extract position from the tree node if available
+ line_num = 0
+ column = 0
+ if hasattr(e.obj, "meta") and e.obj.meta:
+ line_num = e.obj.meta.line
+ column = e.obj.meta.column
+
+ line_text = lines[line_num - 1] if 0 < line_num <= len(lines) else ""
+
+ # Build the error message
+ msg_parts = [f"{filename}:{line_num}:{column}: semantic error"]
+
+ # The original exception is typically a KeyError for undefined types
+ if isinstance(e.orig_exc, KeyError):
+ msg_parts.append(f"Undefined type '{e.orig_exc.args[0]}'")
+ else:
+ msg_parts.append(str(e.orig_exc))
+
+ # Show the offending line with a caret pointing to the error
+ if line_text:
+ msg_parts.append("")
+ msg_parts.append(f" {line_text}")
+ prefix = line_text[: column - 1].expandtabs()
+ msg_parts.append(f" {' ' * len(prefix)}^")
+
+ sys.stderr.write("\n".join(msg_parts) + "\n")
+
+
def xdr_parser() -> Lark:
"""Return a Lark parser instance configured with the XDR language grammar"""