Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/libcst/_parser/grammar.py: 33%

94 statements  

« prev     ^ index     » next       coverage.py v7.3.1, created at 2023-09-25 06:43 +0000

1# Copyright (c) Meta Platforms, Inc. and affiliates. 

2# 

3# This source code is licensed under the MIT license found in the 

4# LICENSE file in the root directory of this source tree. 

5 

6import re 

7from functools import lru_cache 

8from typing import FrozenSet, Iterator, Mapping, Optional, Tuple, Union 

9 

10from libcst._parser.conversions.expression import ( 

11 convert_arg_assign_comp_for, 

12 convert_arglist, 

13 convert_argument, 

14 convert_atom, 

15 convert_atom_basic, 

16 convert_atom_curlybraces, 

17 convert_atom_ellipses, 

18 convert_atom_expr, 

19 convert_atom_expr_await, 

20 convert_atom_expr_trailer, 

21 convert_atom_parens, 

22 convert_atom_squarebrackets, 

23 convert_atom_string, 

24 convert_binop, 

25 convert_boolop, 

26 convert_comp_for, 

27 convert_comp_if, 

28 convert_comp_op, 

29 convert_comparison, 

30 convert_dictorsetmaker, 

31 convert_expression_input, 

32 convert_factor, 

33 convert_fstring, 

34 convert_fstring_content, 

35 convert_fstring_conversion, 

36 convert_fstring_equality, 

37 convert_fstring_expr, 

38 convert_fstring_format_spec, 

39 convert_lambda, 

40 convert_namedexpr_test, 

41 convert_not_test, 

42 convert_power, 

43 convert_sliceop, 

44 convert_star_arg, 

45 convert_star_expr, 

46 convert_subscript, 

47 convert_subscriptlist, 

48 convert_sync_comp_for, 

49 convert_test, 

50 convert_test_nocond, 

51 convert_test_or_expr_list, 

52 convert_testlist_comp_list, 

53 convert_testlist_comp_tuple, 

54 convert_trailer, 

55 convert_trailer_arglist, 

56 convert_trailer_attribute, 

57 convert_trailer_subscriptlist, 

58 convert_yield_arg, 

59 convert_yield_expr, 

60) 

61from libcst._parser.conversions.module import convert_file_input 

62from libcst._parser.conversions.params import ( 

63 convert_argslist, 

64 convert_fpdef, 

65 convert_fpdef_assign, 

66 convert_fpdef_slash, 

67 convert_fpdef_star, 

68 convert_fpdef_starstar, 

69) 

70from libcst._parser.conversions.statement import ( 

71 convert_annassign, 

72 convert_assert_stmt, 

73 convert_assign, 

74 convert_asyncable_funcdef, 

75 convert_asyncable_stmt, 

76 convert_augassign, 

77 convert_break_stmt, 

78 convert_classdef, 

79 convert_compound_stmt, 

80 convert_continue_stmt, 

81 convert_decorated, 

82 convert_decorator, 

83 convert_decorators, 

84 convert_del_stmt, 

85 convert_dotted_as_name, 

86 convert_dotted_as_names, 

87 convert_dotted_name, 

88 convert_except_clause, 

89 convert_expr_stmt, 

90 convert_for_stmt, 

91 convert_funcdef, 

92 convert_funcdef_annotation, 

93 convert_global_stmt, 

94 convert_if_stmt, 

95 convert_if_stmt_elif, 

96 convert_if_stmt_else, 

97 convert_import_as_name, 

98 convert_import_as_names, 

99 convert_import_from, 

100 convert_import_name, 

101 convert_import_relative, 

102 convert_import_stmt, 

103 convert_indented_suite, 

104 convert_nonlocal_stmt, 

105 convert_parameters, 

106 convert_pass_stmt, 

107 convert_raise_stmt, 

108 convert_return_stmt, 

109 convert_simple_stmt_line, 

110 convert_simple_stmt_partial, 

111 convert_simple_stmt_suite, 

112 convert_small_stmt, 

113 convert_stmt, 

114 convert_stmt_input, 

115 convert_suite, 

116 convert_try_stmt, 

117 convert_while_stmt, 

118 convert_with_item, 

119 convert_with_stmt, 

120) 

121from libcst._parser.conversions.terminals import ( 

122 convert_ASYNC, 

123 convert_AWAIT, 

124 convert_DEDENT, 

125 convert_ENDMARKER, 

126 convert_FSTRING_END, 

127 convert_FSTRING_START, 

128 convert_FSTRING_STRING, 

129 convert_INDENT, 

130 convert_NAME, 

131 convert_NEWLINE, 

132 convert_NUMBER, 

133 convert_OP, 

134 convert_STRING, 

135) 

136from libcst._parser.parso.pgen2.generator import generate_grammar, Grammar 

137from libcst._parser.parso.python.token import PythonTokenTypes, TokenType 

138from libcst._parser.parso.utils import parse_version_string, PythonVersionInfo 

139from libcst._parser.production_decorator import get_productions 

140from libcst._parser.types.config import AutoConfig 

141from libcst._parser.types.conversions import NonterminalConversion, TerminalConversion 

142from libcst._parser.types.production import Production 

143 

144# Keep this sorted alphabetically 

145_TERMINAL_CONVERSIONS_SEQUENCE: Tuple[TerminalConversion, ...] = ( 

146 convert_DEDENT, 

147 convert_ENDMARKER, 

148 convert_INDENT, 

149 convert_NAME, 

150 convert_NEWLINE, 

151 convert_NUMBER, 

152 convert_OP, 

153 convert_STRING, 

154 convert_FSTRING_START, 

155 convert_FSTRING_END, 

156 convert_FSTRING_STRING, 

157 convert_ASYNC, 

158 convert_AWAIT, 

159) 

160 

161# Try to match the order of https://docs.python.org/3/reference/grammar.html 

162_NONTERMINAL_CONVERSIONS_SEQUENCE: Tuple[NonterminalConversion, ...] = ( 

163 convert_file_input, 

164 convert_stmt_input, # roughly equivalent to single_input 

165 convert_expression_input, # roughly equivalent to eval_input 

166 convert_stmt, 

167 convert_simple_stmt_partial, 

168 convert_simple_stmt_line, 

169 convert_simple_stmt_suite, 

170 convert_small_stmt, 

171 convert_expr_stmt, 

172 convert_annassign, 

173 convert_augassign, 

174 convert_assign, 

175 convert_pass_stmt, 

176 convert_continue_stmt, 

177 convert_break_stmt, 

178 convert_del_stmt, 

179 convert_import_stmt, 

180 convert_import_name, 

181 convert_import_relative, 

182 convert_import_from, 

183 convert_import_as_name, 

184 convert_dotted_as_name, 

185 convert_import_as_names, 

186 convert_dotted_as_names, 

187 convert_dotted_name, 

188 convert_return_stmt, 

189 convert_raise_stmt, 

190 convert_global_stmt, 

191 convert_nonlocal_stmt, 

192 convert_assert_stmt, 

193 convert_compound_stmt, 

194 convert_if_stmt, 

195 convert_if_stmt_elif, 

196 convert_if_stmt_else, 

197 convert_while_stmt, 

198 convert_for_stmt, 

199 convert_try_stmt, 

200 convert_except_clause, 

201 convert_with_stmt, 

202 convert_with_item, 

203 convert_asyncable_funcdef, 

204 convert_funcdef, 

205 convert_classdef, 

206 convert_decorator, 

207 convert_decorators, 

208 convert_decorated, 

209 convert_asyncable_stmt, 

210 convert_parameters, 

211 convert_argslist, 

212 convert_fpdef_slash, 

213 convert_fpdef_star, 

214 convert_fpdef_starstar, 

215 convert_fpdef_assign, 

216 convert_fpdef, 

217 convert_funcdef_annotation, 

218 convert_suite, 

219 convert_indented_suite, 

220 convert_namedexpr_test, 

221 convert_test, 

222 convert_test_nocond, 

223 convert_lambda, 

224 convert_boolop, 

225 convert_not_test, 

226 convert_comparison, 

227 convert_comp_op, 

228 convert_star_expr, 

229 convert_binop, 

230 convert_factor, 

231 convert_power, 

232 convert_atom_expr, 

233 convert_atom_expr_await, 

234 convert_atom_expr_trailer, 

235 convert_trailer, 

236 convert_trailer_attribute, 

237 convert_trailer_subscriptlist, 

238 convert_subscriptlist, 

239 convert_subscript, 

240 convert_sliceop, 

241 convert_trailer_arglist, 

242 convert_atom, 

243 convert_atom_basic, 

244 convert_atom_parens, 

245 convert_atom_squarebrackets, 

246 convert_atom_curlybraces, 

247 convert_atom_string, 

248 convert_fstring, 

249 convert_fstring_content, 

250 convert_fstring_conversion, 

251 convert_fstring_equality, 

252 convert_fstring_expr, 

253 convert_fstring_format_spec, 

254 convert_atom_ellipses, 

255 convert_testlist_comp_tuple, 

256 convert_testlist_comp_list, 

257 convert_test_or_expr_list, 

258 convert_dictorsetmaker, 

259 convert_arglist, 

260 convert_argument, 

261 convert_arg_assign_comp_for, 

262 convert_star_arg, 

263 convert_sync_comp_for, 

264 convert_comp_for, 

265 convert_comp_if, 

266 convert_yield_expr, 

267 convert_yield_arg, 

268) 

269 

270 

271def get_grammar_str(version: PythonVersionInfo, future_imports: FrozenSet[str]) -> str: 

272 """ 

273 Returns an BNF-like grammar text that `parso.pgen2.generator.generate_grammar` can 

274 handle. 

275 

276 While you should generally use `get_grammar` instead, this can be useful for 

277 debugging the grammar. 

278 """ 

279 lines = [] 

280 for p in get_nonterminal_productions(version, future_imports): 

281 lines.append(str(p)) 

282 return "\n".join(lines) + "\n" 

283 

284 

285# TODO: We should probably provide an on-disk cache like parso and lib2to3 do. Because 

286# of how we're defining our grammar, efficient cache invalidation is harder, though not 

287# impossible. 

288@lru_cache() 

289def get_grammar( 

290 version: PythonVersionInfo, 

291 future_imports: Union[FrozenSet[str], AutoConfig], 

292) -> "Grammar[TokenType]": 

293 if isinstance(future_imports, AutoConfig): 

294 # For easier testing, if not provided assume no __future__ imports 

295 future_imports = frozenset(()) 

296 return generate_grammar(get_grammar_str(version, future_imports), PythonTokenTypes) 

297 

298 

299@lru_cache() 

300def get_terminal_conversions() -> Mapping[str, TerminalConversion]: 

301 """ 

302 Returns a mapping from terminal type name to the conversion function that should be 

303 called by the parser. 

304 """ 

305 return { 

306 # pyre-fixme[16]: Optional type has no attribute `group`. 

307 re.match("convert_(.*)", fn.__name__).group(1): fn 

308 for fn in _TERMINAL_CONVERSIONS_SEQUENCE 

309 } 

310 

311 

312@lru_cache() 

313def validate_grammar() -> None: 

314 for fn in _NONTERMINAL_CONVERSIONS_SEQUENCE: 

315 fn_productions = get_productions(fn) 

316 if all(p.name == fn_productions[0].name for p in fn_productions): 

317 # all the production names are the same, ensure that the `convert_` function 

318 # is named correctly 

319 production_name = fn_productions[0].name 

320 expected_name = f"convert_{production_name}" 

321 if fn.__name__ != expected_name: 

322 raise Exception( 

323 f"The conversion function for '{production_name}' " 

324 + f"must be called '{expected_name}', not '{fn.__name__}'." 

325 ) 

326 

327 

328def _get_version_comparison(version: str) -> Tuple[str, PythonVersionInfo]: 

329 if version[:2] in (">=", "<=", "==", "!="): 

330 return (version[:2], parse_version_string(version[2:].strip())) 

331 if version[:1] in (">", "<"): 

332 return (version[:1], parse_version_string(version[1:].strip())) 

333 raise Exception(f"Invalid version comparison specifier '{version}'") 

334 

335 

336def _compare_versions( 

337 requested_version: PythonVersionInfo, 

338 actual_version: PythonVersionInfo, 

339 comparison: str, 

340) -> bool: 

341 if comparison == ">=": 

342 return actual_version >= requested_version 

343 if comparison == "<=": 

344 return actual_version <= requested_version 

345 if comparison == "==": 

346 return actual_version == requested_version 

347 if comparison == "!=": 

348 return actual_version != requested_version 

349 if comparison == ">": 

350 return actual_version > requested_version 

351 if comparison == "<": 

352 return actual_version < requested_version 

353 raise Exception(f"Invalid version comparison specifier '{comparison}'") 

354 

355 

356def _should_include( 

357 requested_version: Optional[str], actual_version: PythonVersionInfo 

358) -> bool: 

359 if requested_version is None: 

360 return True 

361 for version in requested_version.split(","): 

362 comparison, parsed_version = _get_version_comparison(version.strip()) 

363 if not _compare_versions(parsed_version, actual_version, comparison): 

364 return False 

365 return True 

366 

367 

368def _should_include_future( 

369 future: Optional[str], 

370 future_imports: FrozenSet[str], 

371) -> bool: 

372 if future is None: 

373 return True 

374 if future[:1] == "!": 

375 return future[1:] not in future_imports 

376 return future in future_imports 

377 

378 

379def get_nonterminal_productions( 

380 version: PythonVersionInfo, future_imports: FrozenSet[str] 

381) -> Iterator[Production]: 

382 for conversion in _NONTERMINAL_CONVERSIONS_SEQUENCE: 

383 for production in get_productions(conversion): 

384 if not _should_include(production.version, version): 

385 continue 

386 if not _should_include_future(production.future, future_imports): 

387 continue 

388 yield production 

389 

390 

391@lru_cache() 

392def get_nonterminal_conversions( 

393 version: PythonVersionInfo, 

394 future_imports: FrozenSet[str], 

395) -> Mapping[str, NonterminalConversion]: 

396 """ 

397 Returns a mapping from nonterminal production name to the conversion function that 

398 should be called by the parser. 

399 """ 

400 conversions = {} 

401 for fn in _NONTERMINAL_CONVERSIONS_SEQUENCE: 

402 for fn_production in get_productions(fn): 

403 if not _should_include(fn_production.version, version): 

404 continue 

405 if not _should_include_future(fn_production.future, future_imports): 

406 continue 

407 if fn_production.name in conversions: 

408 raise Exception( 

409 f"Found duplicate '{fn_production.name}' production in grammar" 

410 ) 

411 conversions[fn_production.name] = fn 

412 

413 return conversions