Linux kernel mirror (for testing)
git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel
os
linux
1#!/usr/bin/env python3
2# ex: set filetype=python:
3
4"""Common parsing code for xdrgen"""
5
6import sys
7from typing import Callable
8
9from lark import Lark
10from lark.exceptions import UnexpectedInput, UnexpectedToken, VisitError
11
12
13# Set to True to emit annotation comments in generated source
14annotate = False
15
16# Set to True to emit enum value validation in decoders
17enum_validation = True
18
19# Map internal Lark token names to human-readable names
20TOKEN_NAMES = {
21 "__ANON_0": "identifier",
22 "__ANON_1": "number",
23 "SEMICOLON": "';'",
24 "LBRACE": "'{'",
25 "RBRACE": "'}'",
26 "LPAR": "'('",
27 "RPAR": "')'",
28 "LSQB": "'['",
29 "RSQB": "']'",
30 "LESSTHAN": "'<'",
31 "MORETHAN": "'>'",
32 "EQUAL": "'='",
33 "COLON": "':'",
34 "COMMA": "','",
35 "STAR": "'*'",
36 "$END": "end of file",
37}
38
39
40class XdrParseError(Exception):
41 """Raised when XDR parsing fails"""
42
43
44def set_xdr_annotate(set_it: bool) -> None:
45 """Set 'annotate' if --annotate was specified on the command line"""
46 global annotate
47 annotate = set_it
48
49
50def get_xdr_annotate() -> bool:
51 """Return True if --annotate was specified on the command line"""
52 return annotate
53
54
55def set_xdr_enum_validation(set_it: bool) -> None:
56 """Set 'enum_validation' based on command line options"""
57 global enum_validation
58 enum_validation = set_it
59
60
61def get_xdr_enum_validation() -> bool:
62 """Return True when enum validation is enabled for decoder generation"""
63 return enum_validation
64
65
66def make_error_handler(source: str, filename: str) -> Callable[[UnexpectedInput], bool]:
67 """Create an error handler that reports the first parse error and aborts.
68
69 Args:
70 source: The XDR source text being parsed
71 filename: The name of the file being parsed
72
73 Returns:
74 An error handler function for use with Lark's on_error parameter
75 """
76 lines = source.splitlines()
77
78 def handle_parse_error(e: UnexpectedInput) -> bool:
79 """Report a parse error with context and abort parsing"""
80 line_num = e.line
81 column = e.column
82 line_text = lines[line_num - 1] if 0 < line_num <= len(lines) else ""
83
84 # Build the error message
85 msg_parts = [f"{filename}:{line_num}:{column}: parse error"]
86
87 # Show what was found vs what was expected
88 if isinstance(e, UnexpectedToken):
89 token = e.token
90 if token.type == "__ANON_0":
91 found = f"identifier '{token.value}'"
92 elif token.type == "__ANON_1":
93 found = f"number '{token.value}'"
94 else:
95 found = f"'{token.value}'"
96 msg_parts.append(f"Unexpected {found}")
97
98 # Provide helpful expected tokens list
99 expected = e.expected
100 if expected:
101 readable = [
102 TOKEN_NAMES.get(exp, exp.lower().replace("_", " "))
103 for exp in sorted(expected)
104 ]
105 if len(readable) == 1:
106 msg_parts.append(f"Expected {readable[0]}")
107 elif len(readable) <= 4:
108 msg_parts.append(f"Expected one of: {', '.join(readable)}")
109 else:
110 msg_parts.append(str(e).split("\n")[0])
111
112 # Show the offending line with a caret pointing to the error
113 msg_parts.append("")
114 msg_parts.append(f" {line_text}")
115 prefix = line_text[: column - 1].expandtabs()
116 msg_parts.append(f" {' ' * len(prefix)}^")
117
118 sys.stderr.write("\n".join(msg_parts) + "\n")
119 raise XdrParseError()
120
121 return handle_parse_error
122
123
124def handle_transform_error(e: VisitError, source: str, filename: str) -> None:
125 """Report a transform error with context.
126
127 Args:
128 e: The VisitError from Lark's transformer
129 source: The XDR source text being parsed
130 filename: The name of the file being parsed
131 """
132 lines = source.splitlines()
133
134 # Extract position from the tree node if available
135 line_num = 0
136 column = 0
137 if hasattr(e.obj, "meta") and e.obj.meta:
138 line_num = e.obj.meta.line
139 column = e.obj.meta.column
140
141 line_text = lines[line_num - 1] if 0 < line_num <= len(lines) else ""
142
143 # Build the error message
144 msg_parts = [f"{filename}:{line_num}:{column}: semantic error"]
145
146 # The original exception is typically a KeyError for undefined types
147 if isinstance(e.orig_exc, KeyError):
148 msg_parts.append(f"Undefined type '{e.orig_exc.args[0]}'")
149 else:
150 msg_parts.append(str(e.orig_exc))
151
152 # Show the offending line with a caret pointing to the error
153 if line_text:
154 msg_parts.append("")
155 msg_parts.append(f" {line_text}")
156 prefix = line_text[: column - 1].expandtabs()
157 msg_parts.append(f" {' ' * len(prefix)}^")
158
159 sys.stderr.write("\n".join(msg_parts) + "\n")
160
161
162def xdr_parser() -> Lark:
163 """Return a Lark parser instance configured with the XDR language grammar"""
164
165 return Lark.open(
166 "grammars/xdr.lark",
167 rel_to=__file__,
168 start="specification",
169 debug=True,
170 strict=True,
171 propagate_positions=True,
172 parser="lalr",
173 lexer="contextual",
174 )