Commit 63a5425f authored by Chuck Lever's avatar Chuck Lever
Browse files

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: default avatarChuck Lever <chuck.lever@oracle.com>
parent 3e6397b0
Loading
Loading
Loading
Loading
+7 −1
Original line number Diff line number Diff line
@@ -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
        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)
+7 −1
Original line number Diff line number Diff line
@@ -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
        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)
+7 −1
Original line number Diff line number Diff line
@@ -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
        try:
            transform_parse_tree(parse_tree)
        except VisitError as e:
            handle_transform_error(e, source, args.filename)
            return 1

    return 0
+7 −1
Original line number Diff line number Diff line
@@ -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
        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)
+39 −1
Original line number Diff line number Diff line
@@ -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"""