Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

xdrgen: Improve parse error reporting

The current verbose Lark exception output makes it difficult to
quickly identify and fix syntax errors in XDR specifications. Users
must wade through hundreds of lines of cascading errors to find the
root cause.

Replace this with concise, compiler-style error messages showing
file, line, column, the unexpected token, and the source line with
a caret pointing to the error location.

Before:
Unexpected token Token('__ANON_1', '+1') at line 14, column 35.
Expected one of:
* SEMICOLON
Previous tokens: [Token('__ANON_0', 'LM_MAXSTRLEN')]
[hundreds more cascading errors...]

After:
file.x:14:35: parse error
Unexpected number '+1'

const LM_MAXNAMELEN = LM_MAXSTRLEN+1;
^

The error handler now raises XdrParseError on the first error,
preventing cascading messages that obscure the root cause.

Signed-off-by: Chuck Lever <chuck.lever@oracle.com>

+118 -35
+8 -8
tools/net/sunrpc/xdrgen/subcmds/declarations.py
··· 8 8 9 9 from argparse import Namespace 10 10 from lark import logger 11 - from lark.exceptions import UnexpectedInput 12 11 13 12 from generators.constant import XdrConstantGenerator 14 13 from generators.enum import XdrEnumGenerator ··· 23 24 from xdr_ast import _XdrConstant, _XdrEnum, _XdrPointer 24 25 from xdr_ast import _XdrTypedef, _XdrStruct, _XdrUnion 25 26 from xdr_parse import xdr_parser, set_xdr_annotate 27 + from xdr_parse import make_error_handler, XdrParseError 26 28 27 29 logger.setLevel(logging.INFO) 28 30 ··· 50 50 gen.emit_declaration(definition.value) 51 51 52 52 53 - def handle_parse_error(e: UnexpectedInput) -> bool: 54 - """Simple parse error reporting, no recovery attempted""" 55 - print(e) 56 - return True 57 - 58 - 59 53 def subcmd(args: Namespace) -> int: 60 54 """Generate definitions and declarations""" 61 55 62 56 set_xdr_annotate(args.annotate) 63 57 parser = xdr_parser() 64 58 with open(args.filename, encoding="utf-8") as f: 65 - parse_tree = parser.parse(f.read(), on_error=handle_parse_error) 59 + source = f.read() 60 + try: 61 + parse_tree = parser.parse( 62 + source, on_error=make_error_handler(source, args.filename) 63 + ) 64 + except XdrParseError: 65 + return 1 66 66 ast = transform_parse_tree(parse_tree) 67 67 68 68 gen = XdrHeaderTopGenerator(args.language, args.peer)
+8 -8
tools/net/sunrpc/xdrgen/subcmds/definitions.py
··· 8 8 9 9 from argparse import Namespace 10 10 from lark import logger 11 - from lark.exceptions import UnexpectedInput 12 11 13 12 from generators.constant import XdrConstantGenerator 14 13 from generators.enum import XdrEnumGenerator ··· 23 24 from xdr_ast import _RpcProgram, _XdrConstant, _XdrEnum, _XdrPointer 24 25 from xdr_ast import _XdrTypedef, _XdrStruct, _XdrUnion 25 26 from xdr_parse import xdr_parser, set_xdr_annotate 27 + from xdr_parse import make_error_handler, XdrParseError 26 28 27 29 logger.setLevel(logging.INFO) 28 30 ··· 69 69 gen.emit_maxsize(definition.value) 70 70 71 71 72 - def handle_parse_error(e: UnexpectedInput) -> bool: 73 - """Simple parse error reporting, no recovery attempted""" 74 - print(e) 75 - return True 76 - 77 - 78 72 def subcmd(args: Namespace) -> int: 79 73 """Generate definitions""" 80 74 81 75 set_xdr_annotate(args.annotate) 82 76 parser = xdr_parser() 83 77 with open(args.filename, encoding="utf-8") as f: 84 - parse_tree = parser.parse(f.read(), on_error=handle_parse_error) 78 + source = f.read() 79 + try: 80 + parse_tree = parser.parse( 81 + source, on_error=make_error_handler(source, args.filename) 82 + ) 83 + except XdrParseError: 84 + return 1 85 85 ast = transform_parse_tree(parse_tree) 86 86 87 87 gen = XdrHeaderTopGenerator(args.language, args.peer)
+8 -9
tools/net/sunrpc/xdrgen/subcmds/lint.py
··· 8 8 9 9 from argparse import Namespace 10 10 from lark import logger 11 - from lark.exceptions import UnexpectedInput 12 11 13 - from xdr_parse import xdr_parser 12 + from xdr_parse import xdr_parser, make_error_handler, XdrParseError 14 13 from xdr_ast import transform_parse_tree 15 14 16 15 logger.setLevel(logging.DEBUG) 17 - 18 - 19 - def handle_parse_error(e: UnexpectedInput) -> bool: 20 - """Simple parse error reporting, no recovery attempted""" 21 - print(e) 22 - return True 23 16 24 17 25 18 def subcmd(args: Namespace) -> int: ··· 20 27 21 28 parser = xdr_parser() 22 29 with open(args.filename, encoding="utf-8") as f: 23 - parse_tree = parser.parse(f.read(), on_error=handle_parse_error) 30 + source = f.read() 31 + try: 32 + parse_tree = parser.parse( 33 + source, on_error=make_error_handler(source, args.filename) 34 + ) 35 + except XdrParseError: 36 + return 1 24 37 transform_parse_tree(parse_tree) 25 38 26 39 return 0
+8 -8
tools/net/sunrpc/xdrgen/subcmds/source.py
··· 8 8 9 9 from argparse import Namespace 10 10 from lark import logger 11 - from lark.exceptions import UnexpectedInput 12 11 13 12 from generators.source_top import XdrSourceTopGenerator 14 13 from generators.enum import XdrEnumGenerator ··· 22 23 from xdr_ast import _XdrStruct, _XdrTypedef, _XdrUnion 23 24 24 25 from xdr_parse import xdr_parser, set_xdr_annotate 26 + from xdr_parse import make_error_handler, XdrParseError 25 27 26 28 logger.setLevel(logging.INFO) 27 29 ··· 92 92 # cel: todo: client needs PROC macros 93 93 94 94 95 - def handle_parse_error(e: UnexpectedInput) -> bool: 96 - """Simple parse error reporting, no recovery attempted""" 97 - print(e) 98 - return True 99 - 100 - 101 95 def subcmd(args: Namespace) -> int: 102 96 """Generate encoder and decoder functions""" 103 97 104 98 set_xdr_annotate(args.annotate) 105 99 parser = xdr_parser() 106 100 with open(args.filename, encoding="utf-8") as f: 107 - parse_tree = parser.parse(f.read(), on_error=handle_parse_error) 101 + source = f.read() 102 + try: 103 + parse_tree = parser.parse( 104 + source, on_error=make_error_handler(source, args.filename) 105 + ) 106 + except XdrParseError: 107 + return 1 108 108 ast = transform_parse_tree(parse_tree) 109 109 match args.peer: 110 110 case "server":
+86
tools/net/sunrpc/xdrgen/xdr_parse.py
··· 3 3 4 4 """Common parsing code for xdrgen""" 5 5 6 + import sys 7 + from typing import Callable 8 + 6 9 from lark import Lark 10 + from lark.exceptions import UnexpectedInput, UnexpectedToken 7 11 8 12 9 13 # Set to True to emit annotation comments in generated source 10 14 annotate = False 15 + 16 + # Map internal Lark token names to human-readable names 17 + TOKEN_NAMES = { 18 + "__ANON_0": "identifier", 19 + "__ANON_1": "number", 20 + "SEMICOLON": "';'", 21 + "LBRACE": "'{'", 22 + "RBRACE": "'}'", 23 + "LPAR": "'('", 24 + "RPAR": "')'", 25 + "LSQB": "'['", 26 + "RSQB": "']'", 27 + "LESSTHAN": "'<'", 28 + "MORETHAN": "'>'", 29 + "EQUAL": "'='", 30 + "COLON": "':'", 31 + "COMMA": "','", 32 + "STAR": "'*'", 33 + "$END": "end of file", 34 + } 35 + 36 + 37 + class XdrParseError(Exception): 38 + """Raised when XDR parsing fails""" 11 39 12 40 13 41 def set_xdr_annotate(set_it: bool) -> None: ··· 47 19 def get_xdr_annotate() -> bool: 48 20 """Return True if --annotate was specified on the command line""" 49 21 return annotate 22 + 23 + 24 + def make_error_handler(source: str, filename: str) -> Callable[[UnexpectedInput], bool]: 25 + """Create an error handler that reports the first parse error and aborts. 26 + 27 + Args: 28 + source: The XDR source text being parsed 29 + filename: The name of the file being parsed 30 + 31 + Returns: 32 + An error handler function for use with Lark's on_error parameter 33 + """ 34 + lines = source.splitlines() 35 + 36 + def handle_parse_error(e: UnexpectedInput) -> bool: 37 + """Report a parse error with context and abort parsing""" 38 + line_num = e.line 39 + column = e.column 40 + line_text = lines[line_num - 1] if 0 < line_num <= len(lines) else "" 41 + 42 + # Build the error message 43 + msg_parts = [f"{filename}:{line_num}:{column}: parse error"] 44 + 45 + # Show what was found vs what was expected 46 + if isinstance(e, UnexpectedToken): 47 + token = e.token 48 + if token.type == "__ANON_0": 49 + found = f"identifier '{token.value}'" 50 + elif token.type == "__ANON_1": 51 + found = f"number '{token.value}'" 52 + else: 53 + found = f"'{token.value}'" 54 + msg_parts.append(f"Unexpected {found}") 55 + 56 + # Provide helpful expected tokens list 57 + expected = e.expected 58 + if expected: 59 + readable = [ 60 + TOKEN_NAMES.get(exp, exp.lower().replace("_", " ")) 61 + for exp in sorted(expected) 62 + ] 63 + if len(readable) == 1: 64 + msg_parts.append(f"Expected {readable[0]}") 65 + elif len(readable) <= 4: 66 + msg_parts.append(f"Expected one of: {', '.join(readable)}") 67 + else: 68 + msg_parts.append(str(e).split("\n")[0]) 69 + 70 + # Show the offending line with a caret pointing to the error 71 + msg_parts.append("") 72 + msg_parts.append(f" {line_text}") 73 + prefix = line_text[: column - 1].expandtabs() 74 + msg_parts.append(f" {' ' * len(prefix)}^") 75 + 76 + sys.stderr.write("\n".join(msg_parts) + "\n") 77 + raise XdrParseError() 78 + 79 + return handle_parse_error 50 80 51 81 52 82 def xdr_parser() -> Lark:
-2
tools/net/sunrpc/xdrgen/xdrgen
··· 133 133 try: 134 134 if __name__ == "__main__": 135 135 sys.exit(main()) 136 - except SystemExit: 137 - sys.exit(0) 138 136 except (KeyboardInterrupt, BrokenPipeError): 139 137 sys.exit(1)