From 63a5425ff5e077c54eb2719c735108e2aa1f9eb6 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Fri, 26 Dec 2025 10:19:33 -0500 Subject: [PATCH] xdrgen: Extend error reporting to AST transformation phase 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 --- .../net/sunrpc/xdrgen/subcmds/declarations.py | 8 +++- .../net/sunrpc/xdrgen/subcmds/definitions.py | 8 +++- tools/net/sunrpc/xdrgen/subcmds/lint.py | 8 +++- tools/net/sunrpc/xdrgen/subcmds/source.py | 8 +++- tools/net/sunrpc/xdrgen/xdr_parse.py | 40 ++++++++++++++++++- 5 files changed, 67 insertions(+), 5 deletions(-) diff --git a/tools/net/sunrpc/xdrgen/subcmds/declarations.py b/tools/net/sunrpc/xdrgen/subcmds/declarations.py index 2fd5c255a547b..97ffb76a02f12 100644 --- a/tools/net/sunrpc/xdrgen/subcmds/declarations.py +++ b/tools/net/sunrpc/xdrgen/subcmds/declarations.py @@ -8,6 +8,7 @@ import logging from argparse import Namespace from lark import logger +from lark.exceptions import VisitError from generators.constant import XdrConstantGenerator from generators.enum import XdrEnumGenerator @@ -24,6 +25,7 @@ from xdr_ast import _XdrConstant, _XdrEnum, _XdrPointer 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) @@ -63,7 +65,11 @@ def subcmd(args: Namespace) -> int: ) 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) diff --git a/tools/net/sunrpc/xdrgen/subcmds/definitions.py b/tools/net/sunrpc/xdrgen/subcmds/definitions.py index 8ea5c57cc37ab..d6c2dcd6f78fe 100644 --- a/tools/net/sunrpc/xdrgen/subcmds/definitions.py +++ b/tools/net/sunrpc/xdrgen/subcmds/definitions.py @@ -8,6 +8,7 @@ import logging from argparse import Namespace from lark import logger +from lark.exceptions import VisitError from generators.constant import XdrConstantGenerator from generators.enum import XdrEnumGenerator @@ -24,6 +25,7 @@ from xdr_ast import _RpcProgram, _XdrConstant, _XdrEnum, _XdrPointer 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) @@ -82,7 +84,11 @@ def subcmd(args: Namespace) -> int: ) 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) diff --git a/tools/net/sunrpc/xdrgen/subcmds/lint.py b/tools/net/sunrpc/xdrgen/subcmds/lint.py index 2c48fa57c4e5a..e1da49632e626 100644 --- a/tools/net/sunrpc/xdrgen/subcmds/lint.py +++ b/tools/net/sunrpc/xdrgen/subcmds/lint.py @@ -8,8 +8,10 @@ import logging 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) @@ -27,6 +29,10 @@ def subcmd(args: Namespace) -> int: ) 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 diff --git a/tools/net/sunrpc/xdrgen/subcmds/source.py b/tools/net/sunrpc/xdrgen/subcmds/source.py index bc7d38802df32..08c883f547d7c 100644 --- a/tools/net/sunrpc/xdrgen/subcmds/source.py +++ b/tools/net/sunrpc/xdrgen/subcmds/source.py @@ -8,6 +8,7 @@ import logging from argparse import Namespace from lark import logger +from lark.exceptions import VisitError from generators.source_top import XdrSourceTopGenerator from generators.enum import XdrEnumGenerator @@ -23,6 +24,7 @@ from xdr_ast import _XdrStruct, _XdrTypedef, _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) @@ -105,7 +107,11 @@ def subcmd(args: Namespace) -> int: ) 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) diff --git a/tools/net/sunrpc/xdrgen/xdr_parse.py b/tools/net/sunrpc/xdrgen/xdr_parse.py index 426513be066c0..38724ad5aea26 100644 --- a/tools/net/sunrpc/xdrgen/xdr_parse.py +++ b/tools/net/sunrpc/xdrgen/xdr_parse.py @@ -7,7 +7,7 @@ import sys 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 @@ -107,6 +107,44 @@ def make_error_handler(source: str, filename: str) -> Callable[[UnexpectedInput] 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""" -- 2.47.3