Coverage for trlc/parser.py: 96%

1180 statements  

« prev     ^ index     » next       coverage.py v7.16.2, created at 2026-09-30 11:03 +0000

1#!/usr/bin/env python3 

2# 

3# TRLC - Treat Requirements Like Code 

4# Copyright (C) 2022-2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) 

5# Copyright (C) 2025 Florian Schanda 

6# 

7# This file is part of the TRLC Python Reference Implementation. 

8# 

9# TRLC is free software: you can redistribute it and/or modify it 

10# under the terms of the GNU General Public License as published by 

11# the Free Software Foundation, either version 3 of the License, or 

12# (at your option) any later version. 

13# 

14# TRLC is distributed in the hope that it will be useful, but WITHOUT 

15# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 

16# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public 

17# License for more details. 

18# 

19# You should have received a copy of the GNU General Public License 

20# along with TRLC. If not, see <https://www.gnu.org/licenses/>. 

21 

22import re 

23 

24from trlc.nested import Nested_Lexer 

25from trlc.lexer import Token_Base, Token, Lexer_Base, TRLC_Lexer 

26from trlc.errors import Message_Handler, TRLC_Error 

27from trlc import ast 

28 

29 

30class Markup_Token(Token_Base): 

31 # lobster-trace: LRM.Markup_String_Format 

32 

33 KIND = { 

34 "CHARACTER": "character", 

35 "REFLIST_BEGIN": "[[", 

36 "REFLIST_END": "]]", 

37 "REFLIST_COMMA": ",", 

38 "REFLIST_DOT": ".", 

39 "REFLIST_IDENTIFIER": "identifier", 

40 } 

41 

42 def __init__(self, location, kind, value): 

43 super().__init__(location, kind, value) 

44 assert isinstance(value, str) 

45 

46 

47class Markup_Lexer(Nested_Lexer): 

48 def __init__(self, mh, literal): 

49 super().__init__(mh, literal) 

50 

51 self.in_reflist = False 

52 

53 def file_location(self): 

54 return self.origin_location 

55 

56 def token(self): 

57 # lobster-trace: LRM.Markup_String_Errors 

58 

59 if self.in_reflist: 

60 self.skip_whitespace() 

61 else: 

62 self.advance() 

63 if self.cc is None: 

64 return None 

65 

66 start_pos = self.lexpos 

67 start_line = self.line_no 

68 start_col = self.col_no 

69 

70 if self.cc == "[" and self.nc == "[": 

71 kind = "REFLIST_BEGIN" 

72 self.advance() 

73 if self.in_reflist: 

74 self.mh.lex_error( 

75 self.source_location( 

76 start_line, start_col, start_pos, start_pos + 1 

77 ), 

78 "cannot nest reference lists", 

79 ) 

80 else: 

81 self.in_reflist = True 

82 

83 elif self.cc == "]" and self.nc == "]": 

84 kind = "REFLIST_END" 

85 self.advance() 

86 if self.in_reflist: 

87 self.in_reflist = False 

88 else: 

89 self.mh.lex_error( 

90 self.source_location( 

91 start_line, start_col, start_pos, start_pos + 1 

92 ), 

93 "opening [[ for this ]] found", 

94 ) 

95 

96 elif not self.in_reflist: 

97 kind = "CHARACTER" 

98 

99 elif self.cc == ",": 

100 kind = "REFLIST_COMMA" 

101 

102 elif self.cc == ".": 

103 kind = "REFLIST_DOT" 

104 

105 elif self.is_alpha(self.cc): 105 ↛ 111line 105 didn't jump to line 111 because the condition on line 105 was always true

106 kind = "REFLIST_IDENTIFIER" 

107 while self.nc and (self.is_alnum(self.nc) or self.nc == "_"): 

108 self.advance() 

109 

110 else: 

111 self.mh.lex_error( 

112 self.source_location(start_line, start_col, start_pos, start_pos), 

113 "unexpected character '%s'" % self.cc, 

114 ) 

115 

116 loc = self.source_location(start_line, start_col, start_pos, self.lexpos) 

117 

118 # pylint: disable=possibly-used-before-assignment 

119 return Markup_Token(loc, kind, self.content[start_pos : self.lexpos + 1]) 

120 

121 

122class Parser_Base: 

123 def __init__(self, mh, lexer, eoc_name, token_map, keywords): 

124 assert isinstance(mh, Message_Handler) 

125 assert isinstance(lexer, Lexer_Base) 

126 assert isinstance(eoc_name, str) 

127 assert isinstance(token_map, dict) 

128 assert isinstance(keywords, frozenset) 

129 self.mh = mh 

130 self.lexer = lexer 

131 

132 self.eoc_name = eoc_name 

133 self.language_tokens = token_map 

134 self.language_keywords = keywords 

135 

136 self.ct = None 

137 self.nt = None 

138 self.advance() 

139 

140 def advance(self): 

141 # lobster-trace: LRM.Comments 

142 self.ct = self.nt 

143 while True: 

144 self.nt = self.lexer.token() 

145 if self.nt is None or self.nt.kind != "COMMENT": 

146 break 

147 

148 def skip_until_newline(self): 

149 if self.ct is None: 149 ↛ 150line 149 didn't jump to line 150 because the condition on line 149 was never true

150 return 

151 current_line = self.ct.location.line_no 

152 while self.nt and self.nt.location.line_no == current_line: 

153 self.advance() 

154 

155 def peek(self, kind): 

156 assert kind in self.language_tokens, "%s is not a valid token" % kind 

157 return self.nt is not None and self.nt.kind == kind 

158 

159 def peek_eof(self): 

160 return self.nt is None 

161 

162 def peek_kw(self, value): 

163 assert value in self.language_keywords, "%s is not a valid keyword" % value 

164 return self.peek("KEYWORD") and self.nt.value == value 

165 

166 def match(self, kind): 

167 # lobster-trace: LRM.Matching_Value_Types 

168 

169 assert kind in self.language_tokens, "%s is not a valid token" % kind 

170 if self.nt is None: 

171 if self.ct is None: 171 ↛ 172line 171 didn't jump to line 172 because the condition on line 171 was never true

172 self.mh.error( 

173 self.lexer.file_location(), 

174 "expected %s, encountered %s instead" 

175 % (self.language_tokens[kind], self.eoc_name), 

176 ) 

177 else: 

178 self.mh.error( 

179 self.ct.location, 

180 "expected %s, encountered %s instead" 

181 % (self.language_tokens[kind], self.eoc_name), 

182 ) 

183 elif self.nt.kind != kind: 

184 self.mh.error( 

185 self.nt.location, 

186 "expected %s, encountered %s instead" 

187 % (self.language_tokens[kind], self.language_tokens[self.nt.kind]), 

188 ) 

189 self.advance() 

190 

191 def match_eof(self): 

192 if self.nt is not None: 192 ↛ 193line 192 didn't jump to line 193 because the condition on line 192 was never true

193 self.mh.error( 

194 self.nt.location, 

195 "expected %s, encountered %s instead" 

196 % (self.eoc_name, self.language_tokens[self.nt.kind]), 

197 ) 

198 

199 def match_kw(self, value): 

200 assert value in self.language_keywords, "%s is not a valid keyword" % value 

201 if self.nt is None: 

202 if self.ct is None: 202 ↛ 209line 202 didn't jump to line 209 because the condition on line 202 was always true

203 self.mh.error( 

204 self.lexer.file_location(), 

205 "expected keyword %s, encountered %s instead" 

206 % (value, self.eoc_name), 

207 ) 

208 else: 

209 self.mh.error( 

210 self.ct.location, 

211 "expected keyword %s, encountered %s instead" 

212 % (value, self.eoc_name), 

213 ) 

214 elif self.nt.kind != "KEYWORD": 

215 self.mh.error( 

216 self.nt.location, 

217 "expected keyword %s, encountered %s instead" 

218 % (value, self.language_tokens[self.nt.kind]), 

219 ) 

220 elif self.nt.value != value: 

221 self.mh.error( 

222 self.nt.location, 

223 "expected keyword %s," 

224 " encountered keyword %s instead" % (value, self.nt.value), 

225 ) 

226 self.advance() 

227 

228 

229class Markup_Parser(Parser_Base): 

230 def __init__(self, parent, literal): 

231 assert isinstance(parent, Parser) 

232 super().__init__( 

233 parent.mh, 

234 Markup_Lexer(parent.mh, literal), 

235 eoc_name="end-of-string", 

236 token_map=Markup_Token.KIND, 

237 keywords=frozenset(), 

238 ) 

239 self.parent = parent 

240 self.references = literal.references 

241 

242 def parse_all_references(self): 

243 while self.nt: 

244 if self.peek("CHARACTER"): 

245 self.advance() 

246 else: 

247 self.parse_ref_list() 

248 self.match_eof() 

249 return self.references 

250 

251 def parse_ref_list(self): 

252 self.match("REFLIST_BEGIN") 

253 self.parse_qualified_name() 

254 while self.peek("REFLIST_COMMA"): 

255 self.match("REFLIST_COMMA") 

256 self.parse_qualified_name() 

257 self.match("REFLIST_END") 

258 

259 def parse_qualified_name(self): 

260 # lobster-trace: LRM.Qualified_Name 

261 # lobster-trace: LRM.Valid_Qualifier 

262 # lobster-trace: LRM.Valid_Name 

263 # lobster-trace: LRM.Markup_String_Resolution 

264 # lobster-trace: LRM.Markup_String_Types 

265 

266 self.match("REFLIST_IDENTIFIER") 

267 if self.peek("REFLIST_DOT"): 

268 package = self.parent.stab.lookup_direct( 

269 mh=self.mh, 

270 name=self.ct.value, 

271 error_location=self.ct.location, 

272 required_subclass=ast.Package, 

273 ) 

274 if not self.parent.cu.is_visible(package): 

275 self.mh.error(self.ct.location, "package must be imported before use") 

276 

277 self.match("REFLIST_DOT") 

278 self.match("REFLIST_IDENTIFIER") 

279 else: 

280 package = self.parent.cu.package 

281 

282 ref = ast.Record_Reference( 

283 location=self.ct.location, name=self.ct.value, typ=None, package=package 

284 ) 

285 self.references.append(ref) 

286 

287 

288class Parser(Parser_Base): 

289 COMPARISON_OPERATOR = ("==", "!=", "<", "<=", ">", ">=") 

290 ADDING_OPERATOR = ("+", "-") 

291 MULTIPLYING_OPERATOR = ("*", "/", "%") 

292 

293 # Keyword that introduces the package indication; overridden by the 

294 # markdown parser, where the H1 heading takes this role. 

295 PACKAGE_KEYWORD = "package" 

296 

297 def __init__( 

298 self, 

299 mh, 

300 stab, 

301 file_name, 

302 lint_mode, 

303 error_recovery, 

304 primary_file=True, 

305 lexer=None, 

306 ): 

307 assert isinstance(mh, Message_Handler) 

308 assert isinstance(stab, ast.Symbol_Table) 

309 assert isinstance(file_name, str) 

310 assert isinstance(lint_mode, bool) 

311 assert isinstance(error_recovery, bool) 

312 assert isinstance(primary_file, bool) 

313 assert isinstance(lexer, TRLC_Lexer) or lexer is None 

314 if lexer: 314 ↛ 323line 314 didn't jump to line 323 because the condition on line 314 was always true

315 super().__init__( 

316 mh, 

317 lexer, 

318 eoc_name="end-of-file", 

319 token_map=Token.KIND, 

320 keywords=TRLC_Lexer.KEYWORDS, 

321 ) 

322 else: 

323 super().__init__( 

324 mh, 

325 TRLC_Lexer(mh, file_name), 

326 eoc_name="end-of-file", 

327 token_map=Token.KIND, 

328 keywords=TRLC_Lexer.KEYWORDS, 

329 ) 

330 

331 self.lint_mode = lint_mode 

332 self.error_recovery = error_recovery 

333 

334 self.stab = stab 

335 self.cu = ast.Compilation_Unit(file_name) 

336 

337 self.primary = primary_file 

338 self.secondary = False 

339 # Controls if the file is actually fully parsed: primary means 

340 # it was selected on the command-line and secondary means it 

341 # was selected by dependency analysis. 

342 

343 self.builtin_bool = stab.lookup_assuming(self.mh, "Boolean") 

344 self.builtin_int = stab.lookup_assuming(self.mh, "Integer") 

345 self.builtin_decimal = stab.lookup_assuming(self.mh, "Decimal") 

346 self.builtin_str = stab.lookup_assuming(self.mh, "String") 

347 self.builtin_mstr = stab.lookup_assuming(self.mh, "Markup_String") 

348 

349 self.section = [] 

350 self.default_scope = ast.Scope() 

351 self.default_scope.push(self.stab) 

352 

353 # "rsl" or "trlc"; set by parse_preamble. Remains None only until 

354 # parse_preamble runs; code that reads this (e.g. 

355 # check_not_self_descendant) is only reachable from parse_rsl_file / 

356 # parse_trlc_file, which the Source_Manager only calls on this same 

357 # parser instance after its parse_preamble call has completed 

358 # successfully, so it is never observed as None there. 

359 self.file_kind = None 

360 

361 def parse_described_name(self): 

362 # lobster-trace: LRM.Described_Names 

363 # lobster-trace: LRM.Described_Name_Description 

364 self.match("IDENTIFIER") 

365 name = self.ct 

366 

367 if self.peek("STRING"): 

368 self.match("STRING") 

369 t_descr = self.ct 

370 return name, t_descr.value, t_descr 

371 else: 

372 return name, None, None 

373 

374 def parse_dotted_name(self): 

375 """Parse a dotted package name: ``IDENTIFIER { '.' IDENTIFIER }``. 

376 

377 :returns: ``(full_name, first_location, tokens)`` 

378 :rtype: tuple[str, Location, list[Token]] 

379 """ 

380 # lobster-trace: LRM.Nested_Package_Names 

381 self.match("IDENTIFIER") 

382 parts = [self.ct.value] 

383 first_location = self.ct.location 

384 tokens = [self.ct] 

385 

386 while self.peek("DOT"): 

387 self.match("DOT") 

388 tokens.append(self.ct) 

389 self.match("IDENTIFIER") 

390 parts.append(self.ct.value) 

391 tokens.append(self.ct) 

392 

393 return ".".join(parts), first_location, tokens 

394 

395 def parse_import_name(self): 

396 """Parse an import target: a dotted package name with an optional 

397 trailing ``.*`` wildcard. 

398 

399 Consumes ``IDENTIFIER { '.' IDENTIFIER } [ '.' '*' ]``. The ``*`` 

400 lexes as an ``OPERATOR`` token with value ``"*"``. 

401 

402 :returns: ``(full_name, first_location, tokens, is_wildcard)`` 

403 :rtype: tuple[str, Location, list[Token], bool] 

404 """ 

405 # lobster-trace: LRM.Wildcard_Import 

406 # lobster-trace: LRM.Nested_Package_Names 

407 self.match("IDENTIFIER") 

408 parts = [self.ct.value] 

409 first_location = self.ct.location 

410 tokens = [self.ct] 

411 is_wildcard = False 

412 

413 while self.peek("DOT"): 

414 self.match("DOT") 

415 t_dot = self.ct 

416 if self.peek("OPERATOR") and self.nt.value == "*": 

417 self.match("OPERATOR") 

418 tokens.append(t_dot) 

419 tokens.append(self.ct) 

420 is_wildcard = True 

421 break 

422 tokens.append(t_dot) 

423 self.match("IDENTIFIER") 

424 parts.append(self.ct.value) 

425 tokens.append(self.ct) 

426 

427 return ".".join(parts), first_location, tokens, is_wildcard 

428 

429 def check_not_self_descendant(self, pkg, t_pkg): 

430 # lobster-trace: LRM.Self_Descendant_Reference 

431 # An rsl file is always elaborated before the rsl files of its 

432 # sub-packages, so their declarations do not exist yet. Without 

433 # this check the user would get a confusing "unknown symbol" 

434 # error for the member instead. 

435 if self.file_kind != "rsl": 

436 return 

437 if not pkg.name.startswith(self.cu.package.name + "."): 

438 return 

439 self.mh.error( 

440 t_pkg.location, 

441 "cannot refer to sub-package %s of the current package" % pkg.name, 

442 explanation="package %s is elaborated before its sub-packages, " 

443 "so %s cannot be used here; move the declarations you need " 

444 "into %s itself, or into a package outside the %s subtree" 

445 % ( 

446 self.cu.package.name, 

447 pkg.name, 

448 self.cu.package.name, 

449 self.cu.package.name, 

450 ), 

451 ) 

452 

453 def descend_sub_packages(self, pkg, t_pkg): 

454 # lobster-trace: LRM.Qualified_Name 

455 # lobster-trace: LRM.Nested_Visibility 

456 # Greedily consume `. segment` pairs, descending into sub-packages 

457 # for as long as a segment names one. The first segment that is not 

458 # a sub-package is the member name; we check that the package it 

459 # belongs to is visible and mark the import used. Returns 

460 # (leaf_package, t_member). If the dotted name ends on a package the 

461 # trailing token (self.ct) is returned as the member, so the caller 

462 # produces a sensible "unknown symbol" error. 

463 # 

464 # t_pkg is the token that named the current pkg; it is used as the 

465 # location for the "package must be imported" error so the caret 

466 # points at the offending package rather than the member. 

467 while self.peek("DOT"): 467 ↛ 490line 467 didn't jump to line 490 because the condition on line 467 was always true

468 self.match("DOT") 

469 t_dot = self.ct 

470 self.match("IDENTIFIER") 

471 t_member = self.ct 

472 child = pkg.sub_packages.lookup_sub_package(t_member.value) 

473 if child is None: 

474 pkg.set_ast_link(t_dot) 

475 if not self.cu.is_visible(pkg): 

476 self.mh.error( 

477 t_pkg.location, 

478 "package must be imported before use", 

479 explanation="add 'import %s' to the " 

480 "preamble of this file" % pkg.name, 

481 ) 

482 self.check_not_self_descendant(pkg, t_pkg) 

483 self.cu.mark_import_used(pkg) 

484 return pkg, t_member 

485 child.set_ast_link(t_dot) 

486 child.set_ast_link(t_member) 

487 pkg = child 

488 t_pkg = t_member 

489 

490 if not self.cu.is_visible(pkg): 

491 self.mh.error( 

492 t_pkg.location, 

493 "package must be imported before use", 

494 explanation="add 'import %s' to the preamble of this file" % pkg.name, 

495 ) 

496 self.check_not_self_descendant(pkg, t_pkg) 

497 self.cu.mark_import_used(pkg) 

498 return pkg, self.ct 

499 

500 def parse_qualified_name(self, scope, required_subclass=None, match_ident=True): 

501 # lobster-trace: LRM.Qualified_Name 

502 # lobster-trace: LRM.Valid_Qualifier 

503 # lobster-trace: LRM.Valid_Name 

504 assert isinstance(scope, ast.Scope) 

505 assert required_subclass is None or isinstance(required_subclass, type) 

506 assert isinstance(match_ident, bool) 

507 

508 if match_ident: 

509 self.match("IDENTIFIER") 

510 sym = scope.lookup(self.mh, self.ct) 

511 sym.set_ast_link(self.ct) 

512 

513 if isinstance(sym, ast.Package): 

514 pkg, t_member = self.descend_sub_packages(sym, self.ct) 

515 return pkg.symbols.lookup(self.mh, t_member, required_subclass) 

516 else: 

517 # Easiest way to generate the correct error message 

518 return scope.lookup(self.mh, self.ct, required_subclass) 

519 

520 def parse_type_declaration(self): 

521 # lobster-trace: LRM.Type_Declarations 

522 if self.peek_kw("enum"): 

523 n_item = self.parse_enum_declaration() 

524 elif self.peek_kw("tuple"): 

525 n_item = self.parse_tuple_declaration() 

526 else: 

527 n_item = self.parse_record_declaration() 

528 assert isinstance(n_item, ast.Concrete_Type) 

529 return n_item 

530 

531 def parse_enum_declaration(self): 

532 # lobster-trace: LRM.Enumeration_Declaration 

533 self.match_kw("enum") 

534 t_enum = self.ct 

535 name, description, t_description = self.parse_described_name() 

536 

537 enum = ast.Enumeration_Type( 

538 name=name.value, 

539 description=description, 

540 location=name.location, 

541 package=self.cu.package, 

542 ) 

543 self.cu.package.symbols.register(self.mh, enum) 

544 enum.set_ast_link(t_enum) 

545 enum.set_ast_link(name) 

546 if t_description: 

547 enum.set_ast_link(t_description) 

548 

549 self.match("C_BRA") 

550 enum.set_ast_link(self.ct) 

551 empty = True 

552 while not self.peek("C_KET"): 

553 name, description, t_description = self.parse_described_name() 

554 lit = ast.Enumeration_Literal_Spec( 

555 name=name.value, 

556 description=description, 

557 location=name.location, 

558 enum=enum, 

559 ) 

560 lit.set_ast_link(name) 

561 if t_description: 

562 lit.set_ast_link(self.ct) 

563 empty = False 

564 enum.literals.register(self.mh, lit) 

565 self.match("C_KET") 

566 enum.set_ast_link(self.ct) 

567 

568 if empty: 

569 # lobster-trace: LRM.No_Empty_Enumerations 

570 self.mh.error(enum.location, "empty enumerations are not permitted") 

571 

572 return enum 

573 

574 def parse_tuple_field( 

575 self, n_tuple, optional_allowed, optional_reason, optional_required 

576 ): 

577 assert isinstance(n_tuple, ast.Tuple_Type) 

578 assert isinstance(optional_allowed, bool) 

579 assert isinstance(optional_reason, str) 

580 assert isinstance(optional_required, bool) 

581 assert optional_allowed or not optional_required 

582 

583 field_name, field_description, t_descr = self.parse_described_name() 

584 

585 if optional_required or self.peek_kw("optional"): 

586 self.match_kw("optional") 

587 t_optional = self.ct 

588 field_is_optional = True 

589 if not optional_allowed: 

590 self.mh.error(self.ct.location, optional_reason) 

591 else: 

592 field_is_optional = False 

593 t_optional = None 

594 

595 # lobster-trace: LRM.Tuple_Field_Types 

596 # S_BRA here means a union type '[T1, T2, ...]', not array bounds. 

597 if self.peek("S_BRA"): 

598 # lobster-trace: LRM.union_type 

599 field_type = self.parse_union_type() 

600 else: 

601 field_type = self.parse_qualified_name(self.default_scope, ast.Type) 

602 comp = ast.Composite_Component( 

603 name=field_name.value, 

604 description=field_description, 

605 location=field_name.location, 

606 member_of=n_tuple, 

607 n_typ=field_type, 

608 optional=field_is_optional, 

609 ) 

610 comp.set_ast_link(field_name) 

611 if t_descr: 

612 comp.set_ast_link(t_descr) 

613 if field_is_optional: 

614 comp.set_ast_link(t_optional) 

615 

616 return comp 

617 

618 def parse_tuple_declaration(self): 

619 # lobster-trace: LRM.Tuple_Declaration 

620 self.match_kw("tuple") 

621 t_tuple = self.ct 

622 name, description, t_descr = self.parse_described_name() 

623 

624 n_tuple = ast.Tuple_Type( 

625 name=name.value, 

626 description=description, 

627 location=name.location, 

628 package=self.cu.package, 

629 ) 

630 

631 n_tuple.set_ast_link(t_tuple) 

632 n_tuple.set_ast_link(name) 

633 if t_descr: 

634 n_tuple.set_ast_link(t_descr) 

635 self.match("C_BRA") 

636 n_tuple.set_ast_link(self.ct) 

637 

638 n_field = self.parse_tuple_field( 

639 n_tuple, 

640 optional_allowed=False, 

641 optional_reason="first field may not be optional", 

642 optional_required=False, 

643 ) 

644 n_tuple.components.register(self.mh, n_field) 

645 

646 has_separators = False 

647 optional_required = False 

648 separator_allowed = True 

649 

650 while self.peek_kw("separator") or self.peek("IDENTIFIER"): 

651 if has_separators or self.peek_kw("separator"): 

652 has_separators = True 

653 self.match_kw("separator") 

654 t_sep = self.ct 

655 if not separator_allowed: 

656 # lobster-trace: LRM.Tuple_Separators_All_Or_None 

657 self.mh.error( 

658 self.ct.location, "either all fields must be separated, or none" 

659 ) 

660 if ( 660 ↛ 674line 660 didn't jump to line 674 because the condition on line 660 was always true

661 self.peek("IDENTIFIER") 

662 or self.peek("AT") 

663 or self.peek("COLON") 

664 or self.peek("SEMICOLON") 

665 ): 

666 self.advance() 

667 sep = ast.Separator(self.ct) 

668 sep.set_ast_link(t_sep) 

669 sep.set_ast_link(self.ct) 

670 n_tuple.add_separator(sep) 

671 else: 

672 separator_allowed = False 

673 # lobster-trace: LRM.Tuple_Optional_Requires_Separators 

674 n_field = self.parse_tuple_field( 

675 n_tuple, 

676 optional_allowed=has_separators, 

677 optional_reason=("optional only permitted in tuples with separators"), 

678 optional_required=optional_required, 

679 ) 

680 n_tuple.components.register(self.mh, n_field) 

681 # lobster-trace: LRM.Tuple_Optional_Fields 

682 optional_required |= n_field.optional 

683 

684 self.match("C_KET") 

685 n_tuple.set_ast_link(self.ct) 

686 

687 # Final check to ban tuples with separators containing other 

688 # tuples. 

689 if has_separators: 

690 # lobster-trace: LRM.Restricted_Tuple_Nesting 

691 for n_field in n_tuple.components.values(): 

692 if ( 

693 isinstance(n_field.n_typ, ast.Tuple_Type) 

694 and n_field.n_typ.has_separators() 

695 ): 

696 self.mh.error( 

697 n_field.location, 

698 "tuple type %s, which contains separators," 

699 " may not contain another tuple with separators" % n_tuple.name, 

700 ) 

701 

702 # Late registration to avoid recursion in tuples 

703 # lobster-trace: LRM.Tuple_Field_Types 

704 self.cu.package.symbols.register(self.mh, n_tuple) 

705 

706 return n_tuple 

707 

708 def parse_union_type(self): 

709 """Parse a union type declaration: '[' Type1 ',' Type2 ... ']' 

710 

711 The leading S_BRA must be the next token when called. 

712 Returns an ast.Union_Type node. 

713 """ 

714 # lobster-trace: LRM.union_type 

715 # lobster-trace: LRM.Union_Type_No_Duplicates 

716 # lobster-trace: LRM.Union_Type_Record_Types_Only 

717 self.match("S_BRA") 

718 t_s_bra = self.ct 

719 

720 union_type_entries = [] # list of (Record_Type, Location) 

721 first_type = self.parse_qualified_name(self.default_scope, ast.Record_Type) 

722 first_type.set_ast_link(self.ct) 

723 union_type_entries.append((first_type, self.ct.location)) 

724 

725 while self.peek("COMMA"): 

726 self.match("COMMA") 

727 next_type = self.parse_qualified_name(self.default_scope, ast.Record_Type) 

728 next_type.set_ast_link(self.ct) 

729 union_type_entries.append((next_type, self.ct.location)) 

730 

731 self.match("S_KET") 

732 t_s_ket = self.ct 

733 

734 seen = {} 

735 for t, loc in union_type_entries: 

736 fqn = t.fully_qualified_name() 

737 if fqn in seen: 

738 self.mh.error(loc, "duplicate type %s in union" % t.name, fatal=False) 

739 else: 

740 seen[fqn] = loc 

741 

742 union_types = [t for t, _ in union_type_entries] 

743 c_typ = ast.Union_Type(location=t_s_bra.location, types=union_types) 

744 c_typ.set_ast_link(t_s_bra) 

745 c_typ.set_ast_link(t_s_ket) 

746 return c_typ 

747 

748 def parse_record_component(self, n_record): 

749 assert isinstance(n_record, ast.Record_Type) 

750 

751 c_name, c_descr, t_descr = self.parse_described_name() 

752 t_optional = None 

753 c_optional = False 

754 if self.peek_kw("optional"): 

755 self.match_kw("optional") 

756 t_optional = self.ct 

757 c_optional = True 

758 

759 # S_BRA here means a union type '[T1, T2, ...]', not array bounds. 

760 # Array bounds '[INTEGER..INTEGER]' are checked in the next block. 

761 if self.peek("S_BRA"): 

762 c_typ = self.parse_union_type() 

763 else: 

764 c_typ = self.parse_qualified_name(self.default_scope, ast.Type) 

765 c_typ.set_ast_link(self.ct) 

766 

767 if self.peek("S_BRA"): 

768 self.match("S_BRA") 

769 t_s_bra = self.ct 

770 self.match("INTEGER") 

771 t_lo = self.ct 

772 a_lo = self.ct.value 

773 loc_lo = self.ct.location 

774 self.match("RANGE") 

775 t_range = self.ct 

776 a_loc = self.ct.location 

777 a_hi = None 

778 if self.peek("INTEGER"): 

779 self.match("INTEGER") 

780 a_hi = self.ct.value 

781 elif self.peek("OPERATOR") and self.nt.value == "*": 781 ↛ 784line 781 didn't jump to line 784 because the condition on line 781 was always true

782 self.match("OPERATOR") 

783 else: 

784 self.mh.error(self.nt.location, "expected INTEGER or * for upper bound") 

785 t_hi = self.ct 

786 loc_hi = self.ct.location 

787 self.match("S_KET") 

788 t_s_ket = self.ct 

789 c_typ = ast.Array_Type( 

790 location=a_loc, 

791 element_type=c_typ, 

792 lower_bound=a_lo, 

793 upper_bound=a_hi, 

794 loc_lower=loc_lo, 

795 loc_upper=loc_hi, 

796 ) 

797 c_typ.set_ast_link(t_s_bra) 

798 c_typ.set_ast_link(t_lo) 

799 c_typ.set_ast_link(t_range) 

800 c_typ.set_ast_link(t_hi) 

801 c_typ.set_ast_link(t_s_ket) 

802 

803 c_comp = ast.Composite_Component( 

804 name=c_name.value, 

805 description=c_descr, 

806 location=c_name.location, 

807 member_of=n_record, 

808 n_typ=c_typ, 

809 optional=c_optional, 

810 ) 

811 c_comp.set_ast_link(c_name) 

812 if t_descr: 

813 c_comp.set_ast_link(t_descr) 

814 if c_optional: 

815 c_comp.set_ast_link(t_optional) 

816 

817 return c_comp 

818 

819 def parse_record_declaration(self): 

820 t_abstract = None 

821 t_final = None 

822 is_abstract = False 

823 is_final = False 

824 if self.peek_kw("abstract"): 

825 self.match_kw("abstract") 

826 t_abstract = self.ct 

827 is_abstract = True 

828 elif self.peek_kw("final"): 

829 self.match_kw("final") 

830 t_final = self.ct 

831 is_final = True 

832 

833 self.match_kw("type") 

834 t_type = self.ct 

835 name, description, t_description = self.parse_described_name() 

836 

837 if self.peek_kw("extends"): 

838 self.match_kw("extends") 

839 t_extends = self.ct 

840 root_record = self.parse_qualified_name(self.default_scope, ast.Record_Type) 

841 root_record.set_ast_link(t_extends) 

842 root_record.set_ast_link(self.ct) 

843 else: 

844 root_record = None 

845 

846 if self.lint_mode and root_record and root_record.is_final and not is_final: 

847 self.mh.check( 

848 name.location, 

849 "consider clarifying that this record is final", 

850 "clarify_final", 

851 ( 

852 "Parent record %s is final, making this record\n" 

853 "also final. Marking it explicitly as final\n" 

854 "clarifies this to casual readers." 

855 % root_record.fully_qualified_name() 

856 ), 

857 ) 

858 

859 record = ast.Record_Type( 

860 name=name.value, 

861 description=description, 

862 location=name.location, 

863 package=self.cu.package, 

864 n_parent=root_record, 

865 is_abstract=is_abstract, 

866 ) 

867 self.cu.package.symbols.register(self.mh, record) 

868 if is_abstract: 

869 record.set_ast_link(t_abstract) 

870 if is_final: 

871 record.set_ast_link(t_final) 

872 record.set_ast_link(t_type) 

873 record.set_ast_link(name) 

874 if t_description: 

875 record.set_ast_link(t_description) 

876 

877 self.match("C_BRA") 

878 record.set_ast_link(self.ct) 

879 while not self.peek("C_KET"): 

880 if self.peek_kw("freeze"): 

881 self.match_kw("freeze") 

882 t_freeze = self.ct 

883 self.match("IDENTIFIER") 

884 n_comp = record.components.lookup( 

885 self.mh, self.ct, ast.Composite_Component 

886 ) 

887 if record.is_frozen(n_comp): 

888 n_value = record.get_freezing_expression(n_comp) 

889 self.mh.error( 

890 self.ct.location, 

891 "duplicate freezing of %s, previously frozen at %s" 

892 % (n_comp.name, self.mh.cross_file_reference(n_value.location)), 

893 ) 

894 n_comp.set_ast_link(t_freeze) 

895 n_comp.set_ast_link(self.ct) 

896 self.match("ASSIGN") 

897 n_comp.set_ast_link(self.ct) 

898 n_value = self.parse_value(n_comp.n_typ) 

899 n_value.set_ast_link(self.ct) 

900 

901 record.frozen[n_comp.name] = n_value 

902 

903 else: 

904 n_comp = self.parse_record_component(record) 

905 if record.is_final: 

906 self.mh.error( 

907 n_comp.location, 

908 "cannot declare new components in final record type", 

909 ) 

910 else: 

911 record.components.register(self.mh, n_comp) 

912 

913 self.match("C_KET") 

914 record.set_ast_link(self.ct) 

915 

916 # Finally mark record final if applicable 

917 if is_final: 

918 record.is_final = True 

919 

920 return record 

921 

922 def parse_expression(self, scope): 

923 # lobster-trace: LRM.Expression 

924 assert isinstance(scope, ast.Scope) 

925 

926 n_lhs = self.parse_relation(scope) 

927 

928 if self.peek_kw("and"): 

929 while self.peek_kw("and"): 

930 self.match_kw("and") 

931 t_op = self.ct 

932 a_op = ast.Binary_Operator.LOGICAL_AND 

933 t_op.ast_link = a_op 

934 n_rhs = self.parse_relation(scope) 

935 n_lhs = ast.Binary_Expression( 

936 mh=self.mh, 

937 location=t_op.location, 

938 typ=self.builtin_bool, 

939 operator=a_op, 

940 n_lhs=n_lhs, 

941 n_rhs=n_rhs, 

942 ) 

943 

944 elif self.peek_kw("or"): 

945 while self.peek_kw("or"): 

946 self.match_kw("or") 

947 t_op = self.ct 

948 a_op = ast.Binary_Operator.LOGICAL_OR 

949 t_op.ast_link = a_op 

950 n_rhs = self.parse_relation(scope) 

951 n_lhs = ast.Binary_Expression( 

952 mh=self.mh, 

953 location=t_op.location, 

954 typ=self.builtin_bool, 

955 operator=a_op, 

956 n_lhs=n_lhs, 

957 n_rhs=n_rhs, 

958 ) 

959 

960 elif self.peek_kw("xor"): 

961 self.match_kw("xor") 

962 t_op = self.ct 

963 a_op = ast.Binary_Operator.LOGICAL_XOR 

964 t_op.ast_link = a_op 

965 n_rhs = self.parse_relation(scope) 

966 n_lhs = ast.Binary_Expression( 

967 mh=self.mh, 

968 location=t_op.location, 

969 typ=self.builtin_bool, 

970 operator=a_op, 

971 n_lhs=n_lhs, 

972 n_rhs=n_rhs, 

973 ) 

974 

975 elif self.peek_kw("implies"): 

976 self.match_kw("implies") 

977 t_op = self.ct 

978 a_op = ast.Binary_Operator.LOGICAL_IMPLIES 

979 t_op.ast_link = a_op 

980 n_rhs = self.parse_relation(scope) 

981 n_lhs = ast.Binary_Expression( 

982 mh=self.mh, 

983 location=t_op.location, 

984 typ=self.builtin_bool, 

985 operator=a_op, 

986 n_lhs=n_lhs, 

987 n_rhs=n_rhs, 

988 ) 

989 

990 return n_lhs 

991 

992 def parse_relation(self, scope): 

993 # lobster-trace: LRM.Relation 

994 # lobster-trace: LRM.Operators 

995 assert isinstance(scope, ast.Scope) 

996 relop_mapping = { 

997 "==": ast.Binary_Operator.COMP_EQ, 

998 "!=": ast.Binary_Operator.COMP_NEQ, 

999 "<": ast.Binary_Operator.COMP_LT, 

1000 "<=": ast.Binary_Operator.COMP_LEQ, 

1001 ">": ast.Binary_Operator.COMP_GT, 

1002 ">=": ast.Binary_Operator.COMP_GEQ, 

1003 } 

1004 assert set(relop_mapping) == set(Parser.COMPARISON_OPERATOR) 

1005 

1006 n_lhs = self.parse_simple_expression(scope) 

1007 

1008 if self.peek("OPERATOR") and self.nt.value in Parser.COMPARISON_OPERATOR: 

1009 self.match("OPERATOR") 

1010 t_op = self.ct 

1011 a_op = relop_mapping[t_op.value] 

1012 t_op.ast_link = a_op 

1013 n_rhs = self.parse_simple_expression(scope) 

1014 return ast.Binary_Expression( 

1015 mh=self.mh, 

1016 location=t_op.location, 

1017 typ=self.builtin_bool, 

1018 operator=a_op, 

1019 n_lhs=n_lhs, 

1020 n_rhs=n_rhs, 

1021 ) 

1022 

1023 elif self.peek_kw("not") or self.peek_kw("in"): 

1024 if self.peek_kw("not"): 

1025 self.match_kw("not") 

1026 t_not = self.ct 

1027 else: 

1028 t_not = None 

1029 

1030 self.match_kw("in") 

1031 t_in = self.ct 

1032 

1033 n_a = self.parse_simple_expression(scope) 

1034 t_n_a = self.ct 

1035 if self.peek("RANGE"): 

1036 self.match("RANGE") 

1037 t_range = self.ct 

1038 n_b = self.parse_simple_expression(scope) 

1039 n_b.set_ast_link(self.ct) 

1040 n_a.set_ast_link(t_n_a) 

1041 rv = ast.Range_Test( 

1042 mh=self.mh, 

1043 location=t_in.location, 

1044 typ=self.builtin_bool, 

1045 n_lhs=n_lhs, 

1046 n_lower=n_a, 

1047 n_upper=n_b, 

1048 ) 

1049 rv.set_ast_link(t_range) 

1050 rv.set_ast_link(t_in) 

1051 

1052 elif isinstance(n_a.typ, ast.Builtin_String): 

1053 rv = ast.Binary_Expression( 

1054 mh=self.mh, 

1055 location=t_in.location, 

1056 typ=self.builtin_bool, 

1057 operator=ast.Binary_Operator.STRING_CONTAINS, 

1058 n_lhs=n_lhs, 

1059 n_rhs=n_a, 

1060 ) 

1061 rv.set_ast_link(t_in) 

1062 

1063 elif isinstance(n_a.typ, ast.Array_Type): 1063 ↛ 1076line 1063 didn't jump to line 1076 because the condition on line 1063 was always true

1064 a_op = ast.Binary_Operator.ARRAY_CONTAINS 

1065 t_in.ast_link = a_op 

1066 rv = ast.Binary_Expression( 

1067 mh=self.mh, 

1068 location=t_in.location, 

1069 typ=self.builtin_bool, 

1070 operator=a_op, 

1071 n_lhs=n_lhs, 

1072 n_rhs=n_a, 

1073 ) 

1074 

1075 else: 

1076 self.mh.error( 

1077 n_a.location, 

1078 "membership test only defined for Strings and Arrays," 

1079 " not for %s" % n_a.typ.name, 

1080 ) 

1081 

1082 if t_not is not None: 

1083 a_unary_op = ast.Unary_Operator.LOGICAL_NOT 

1084 t_not.ast_link = a_unary_op 

1085 rv = ast.Unary_Expression( 

1086 mh=self.mh, 

1087 location=t_not.location, 

1088 typ=self.builtin_bool, 

1089 operator=a_unary_op, 

1090 n_operand=rv, 

1091 ) 

1092 

1093 return rv 

1094 

1095 else: 

1096 return n_lhs 

1097 

1098 def parse_simple_expression(self, scope): 

1099 # lobster-trace: LRM.Simple_Expression 

1100 # lobster-trace: LRM.Operators 

1101 # lobster-trace: LRM.Unary_Minus_Parsing 

1102 assert isinstance(scope, ast.Scope) 

1103 un_add_map = {"+": ast.Unary_Operator.PLUS, "-": ast.Unary_Operator.MINUS} 

1104 bin_add_map = {"+": ast.Binary_Operator.PLUS, "-": ast.Binary_Operator.MINUS} 

1105 assert set(un_add_map) == set(Parser.ADDING_OPERATOR) 

1106 assert set(bin_add_map) == set(Parser.ADDING_OPERATOR) 

1107 

1108 if self.peek("OPERATOR") and self.nt.value in Parser.ADDING_OPERATOR: 

1109 self.match("OPERATOR") 

1110 t_unary = self.ct 

1111 a_unary = un_add_map[t_unary.value] 

1112 t_unary.ast_link = a_unary 

1113 has_explicit_brackets = self.peek("BRA") 

1114 else: 

1115 t_unary = None 

1116 

1117 n_lhs = self.parse_term(scope) 

1118 if t_unary: 

1119 # pylint: disable=possibly-used-before-assignment 

1120 if ( 

1121 self.lint_mode 

1122 and isinstance(n_lhs, ast.Binary_Expression) 

1123 and not has_explicit_brackets 

1124 ): 

1125 self.mh.check( 

1126 t_unary.location, 

1127 "expression means -(%s), place explicit " 

1128 "brackets to clarify intent" % n_lhs.to_string(), 

1129 "unary_minus_precedence", 

1130 ) 

1131 

1132 n_lhs = ast.Unary_Expression( 

1133 mh=self.mh, 

1134 location=t_unary.location, 

1135 typ=n_lhs.typ, 

1136 operator=a_unary, 

1137 n_operand=n_lhs, 

1138 ) 

1139 

1140 if isinstance(n_lhs.typ, ast.Builtin_String): 

1141 rtyp = self.builtin_str 

1142 else: 

1143 rtyp = n_lhs.typ 

1144 

1145 while self.peek("OPERATOR") and self.nt.value in Parser.ADDING_OPERATOR: 

1146 self.match("OPERATOR") 

1147 t_op = self.ct 

1148 a_op = bin_add_map[t_op.value] 

1149 t_op.ast_link = a_op 

1150 n_rhs = self.parse_term(scope) 

1151 n_lhs = ast.Binary_Expression( 

1152 mh=self.mh, 

1153 location=t_op.location, 

1154 typ=rtyp, 

1155 operator=a_op, 

1156 n_lhs=n_lhs, 

1157 n_rhs=n_rhs, 

1158 ) 

1159 

1160 return n_lhs 

1161 

1162 def parse_term(self, scope): 

1163 # lobster-trace: LRM.Term 

1164 # lobster-trace: LRM.Operators 

1165 assert isinstance(scope, ast.Scope) 

1166 mul_map = { 

1167 "*": ast.Binary_Operator.TIMES, 

1168 "/": ast.Binary_Operator.DIVIDE, 

1169 "%": ast.Binary_Operator.REMAINDER, 

1170 } 

1171 assert set(mul_map) == set(Parser.MULTIPLYING_OPERATOR) 

1172 

1173 n_lhs = self.parse_factor(scope) 

1174 while self.peek("OPERATOR") and self.nt.value in Parser.MULTIPLYING_OPERATOR: 

1175 self.match("OPERATOR") 

1176 t_op = self.ct 

1177 a_op = mul_map[t_op.value] 

1178 t_op.ast_link = a_op 

1179 n_rhs = self.parse_factor(scope) 

1180 n_lhs = ast.Binary_Expression( 

1181 mh=self.mh, 

1182 location=t_op.location, 

1183 typ=n_lhs.typ, 

1184 operator=a_op, 

1185 n_lhs=n_lhs, 

1186 n_rhs=n_rhs, 

1187 ) 

1188 

1189 return n_lhs 

1190 

1191 def parse_factor(self, scope): 

1192 # lobster-trace: LRM.Factor 

1193 assert isinstance(scope, ast.Scope) 

1194 

1195 if self.peek_kw("not"): 

1196 self.match_kw("not") 

1197 t_op = self.ct 

1198 n_operand = self.parse_primary(scope) 

1199 a_not = ast.Unary_Operator.LOGICAL_NOT 

1200 t_op.ast_link = a_not 

1201 return ast.Unary_Expression( 

1202 mh=self.mh, 

1203 location=t_op.location, 

1204 typ=self.builtin_bool, 

1205 operator=a_not, 

1206 n_operand=n_operand, 

1207 ) 

1208 

1209 elif self.peek_kw("abs"): 

1210 self.match_kw("abs") 

1211 t_op = self.ct 

1212 n_operand = self.parse_primary(scope) 

1213 a_abs = ast.Unary_Operator.ABSOLUTE_VALUE 

1214 t_op.ast_link = a_abs 

1215 return ast.Unary_Expression( 

1216 mh=self.mh, 

1217 location=t_op.location, 

1218 typ=n_operand.typ, 

1219 operator=a_abs, 

1220 n_operand=n_operand, 

1221 ) 

1222 

1223 else: 

1224 n_lhs = self.parse_primary(scope) 

1225 if self.peek("OPERATOR") and self.nt.value == "**": 

1226 self.match("OPERATOR") 

1227 t_op = self.ct 

1228 n_rhs = self.parse_primary(scope) 

1229 rhs_value = n_rhs.evaluate(self.mh, None, None) 

1230 a_binary = ast.Binary_Operator.POWER 

1231 t_op.ast_link = a_binary 

1232 n_lhs = ast.Binary_Expression( 

1233 mh=self.mh, 

1234 location=t_op.location, 

1235 typ=n_lhs.typ, 

1236 operator=a_binary, 

1237 n_lhs=n_lhs, 

1238 n_rhs=n_rhs, 

1239 ) 

1240 if rhs_value.value < 0: 1240 ↛ 1241line 1240 didn't jump to line 1241 because the condition on line 1240 was never true

1241 self.mh.error(n_rhs.location, "exponent must not be negative") 

1242 return n_lhs 

1243 

1244 def parse_primary(self, scope): 

1245 # lobster-trace: LRM.Primary 

1246 assert isinstance(scope, ast.Scope) 

1247 

1248 if self.peek("INTEGER"): 

1249 # lobster-trace: LRM.Integer_Values 

1250 self.match("INTEGER") 

1251 int_lit = ast.Integer_Literal(self.ct, self.builtin_int) 

1252 int_lit.set_ast_link(self.ct) 

1253 return int_lit 

1254 

1255 elif self.peek("DECIMAL"): 

1256 # lobster-trace: LRM.Decimal_Values 

1257 self.match("DECIMAL") 

1258 dec_lit = ast.Decimal_Literal(self.ct, self.builtin_decimal) 

1259 dec_lit.set_ast_link(self.ct) 

1260 return dec_lit 

1261 

1262 elif self.peek("STRING"): 

1263 # lobster-trace: LRM.String_Values 

1264 self.match("STRING") 

1265 string_lit = ast.String_Literal(self.ct, self.builtin_str) 

1266 string_lit.set_ast_link(self.ct) 

1267 return string_lit 

1268 

1269 elif self.peek_kw("true") or self.peek_kw("false"): 

1270 # lobster-trace: LRM.Boolean_Values 

1271 self.match("KEYWORD") 

1272 bool_lit = ast.Boolean_Literal(self.ct, self.builtin_bool) 

1273 bool_lit.set_ast_link(self.ct) 

1274 return bool_lit 

1275 

1276 elif self.peek_kw("null"): 

1277 self.match_kw("null") 

1278 null_lit = ast.Null_Literal(self.ct) 

1279 null_lit.set_ast_link(self.ct) 

1280 return null_lit 

1281 

1282 elif self.peek("BRA"): 

1283 self.match("BRA") 

1284 t_bra = self.ct 

1285 if self.peek_kw("forall") or self.peek_kw("exists"): 

1286 rv = self.parse_quantified_expression(scope) 

1287 elif self.peek_kw("if"): 

1288 rv = self.parse_conditional_expression(scope) 

1289 else: 

1290 rv = self.parse_expression(scope) 

1291 rv.set_ast_link(t_bra) 

1292 self.match("KET") 

1293 rv.set_ast_link(self.ct) 

1294 return rv 

1295 

1296 else: 

1297 return self.parse_name(scope) 

1298 

1299 def parse_quantified_expression(self, scope): 

1300 # lobster-trace: LRM.Quantified_Expression 

1301 assert isinstance(scope, ast.Scope) 

1302 

1303 if self.peek_kw("forall"): 

1304 self.match_kw("forall") 

1305 t_quantified = self.ct 

1306 universal = True 

1307 else: 

1308 self.match_kw("exists") 

1309 t_quantified = self.ct 

1310 universal = False 

1311 loc = self.ct.location 

1312 self.match("IDENTIFIER") 

1313 t_qv = self.ct 

1314 if scope.contains(t_qv.value): 

1315 # lobster-trace: LRM.Quantification_Naming_Scope 

1316 pdef = scope.lookup(self.mh, t_qv) 

1317 self.mh.error( 

1318 t_qv.location, 

1319 "shadows %s %s from %s" 

1320 % ( 

1321 pdef.__class__.__name__, 

1322 pdef.name, 

1323 self.mh.cross_file_reference(pdef.location), 

1324 ), 

1325 ) 

1326 self.match_kw("in") 

1327 t_in = self.ct 

1328 self.match("IDENTIFIER") 

1329 field = scope.lookup(self.mh, self.ct, ast.Composite_Component) 

1330 n_source = ast.Name_Reference(self.ct.location, field) 

1331 n_source.set_ast_link(self.ct) 

1332 if not isinstance(field.n_typ, ast.Array_Type): 

1333 # lobster-trace: LRM.Quantification_Object 

1334 self.mh.error(self.ct.location, "you can only quantify over arrays") 

1335 n_var = ast.Quantified_Variable( 

1336 t_qv.value, t_qv.location, field.n_typ.element_type 

1337 ) 

1338 n_var.set_ast_link(t_qv) 

1339 self.match("ARROW") 

1340 t_arrow = self.ct 

1341 

1342 new_table = ast.Symbol_Table() 

1343 new_table.register(self.mh, n_var) 

1344 scope.push(new_table) 

1345 n_expr = self.parse_expression(scope) 

1346 scope.pop() 

1347 

1348 quantified_expression = ast.Quantified_Expression( 

1349 mh=self.mh, 

1350 location=loc, 

1351 typ=self.builtin_bool, 

1352 universal=universal, 

1353 n_variable=n_var, 

1354 n_source=n_source, 

1355 n_expr=n_expr, 

1356 ) 

1357 

1358 quantified_expression.set_ast_link(t_quantified) 

1359 quantified_expression.set_ast_link(t_in) 

1360 quantified_expression.set_ast_link(t_arrow) 

1361 

1362 return quantified_expression 

1363 

1364 def parse_conditional_expression(self, scope): 

1365 # lobster-trace: LRM.Conditional_Expression 

1366 # lobster-trace: LRM.Restricted_Null 

1367 assert isinstance(scope, ast.Scope) 

1368 

1369 self.match_kw("if") 

1370 t_if = self.ct 

1371 if_cond = self.parse_expression(scope) 

1372 self.match_kw("then") 

1373 t_then = self.ct 

1374 if_expr = self.parse_expression(scope) 

1375 if if_expr.typ is None: 

1376 self.mh.error(if_expr.location, "null is not permitted here") 

1377 if_action = ast.Action(self.mh, t_if, if_cond, if_expr) 

1378 

1379 rv = ast.Conditional_Expression(location=t_if.location, if_action=if_action) 

1380 if_action.set_ast_link(t_if) 

1381 if_action.set_ast_link(t_then) 

1382 

1383 while self.peek_kw("elsif"): 

1384 self.match_kw("elsif") 

1385 t_elsif = self.ct 

1386 elsif_cond = self.parse_expression(scope) 

1387 self.match_kw("then") 

1388 t_then = self.ct 

1389 elsif_expr = self.parse_expression(scope) 

1390 elsif_action = ast.Action(self.mh, t_elsif, elsif_cond, elsif_expr) 

1391 elsif_action.set_ast_link(t_elsif) 

1392 elsif_action.set_ast_link(t_then) 

1393 rv.add_elsif(self.mh, elsif_action) 

1394 

1395 self.match_kw("else") 

1396 rv.set_ast_link(self.ct) 

1397 else_expr = self.parse_expression(scope) 

1398 rv.set_else_part(self.mh, else_expr) 

1399 

1400 return rv 

1401 

1402 def parse_builtin(self, scope, n_name, t_name): 

1403 # lobster-trace: LRM.Builtin_Functions 

1404 # lobster-trace: LRM.Builtin_Type_Conversion_Functions 

1405 assert isinstance(scope, ast.Scope) 

1406 assert isinstance(n_name, (ast.Builtin_Function, ast.Builtin_Numeric_Type)) 

1407 assert isinstance(t_name, Token) 

1408 

1409 # Parse the arguments. 

1410 parameters = [] 

1411 n_name.set_ast_link(self.ct) 

1412 self.match("BRA") 

1413 n_name.set_ast_link(self.ct) 

1414 while not self.peek("KET"): 1414 ↛ 1425line 1414 didn't jump to line 1425 because the condition on line 1414 was always true

1415 exp = self.parse_expression(scope) 

1416 if not self.ct.ast_link: 1416 ↛ 1417line 1416 didn't jump to line 1417 because the condition on line 1416 was never true

1417 exp.set_ast_link(self.ct) 

1418 parameters.append(exp) 

1419 

1420 if self.peek("COMMA"): 

1421 self.match("COMMA") 

1422 n_name.set_ast_link(self.ct) 

1423 else: 

1424 break 

1425 self.match("KET") 

1426 n_name.set_ast_link(self.ct) 

1427 

1428 # Enforce arity 

1429 if isinstance(n_name, ast.Builtin_Function): 

1430 required_arity = n_name.arity 

1431 precise = not n_name.arity_at_least 

1432 else: 

1433 required_arity = 1 

1434 precise = True 

1435 

1436 if precise: 

1437 if required_arity != len(parameters): 

1438 self.mh.error( 

1439 t_name.location, "function requires %u parameters" % n_name.arity 

1440 ) 

1441 else: 

1442 if required_arity > len(parameters): 1442 ↛ 1443line 1442 didn't jump to line 1443 because the condition on line 1442 was never true

1443 self.mh.error( 

1444 t_name.location, 

1445 "function requires at least %u parameters" % n_name.arity, 

1446 ) 

1447 

1448 # Enforce types 

1449 if n_name.name == "len": 

1450 if isinstance(parameters[0].typ, ast.Builtin_String): 

1451 return ast.Unary_Expression( 

1452 mh=self.mh, 

1453 location=t_name.location, 

1454 typ=self.builtin_int, 

1455 operator=ast.Unary_Operator.STRING_LENGTH, 

1456 n_operand=parameters[0], 

1457 ) 

1458 else: 

1459 return ast.Unary_Expression( 

1460 mh=self.mh, 

1461 location=t_name.location, 

1462 typ=self.builtin_int, 

1463 operator=ast.Unary_Operator.ARRAY_LENGTH, 

1464 n_operand=parameters[0], 

1465 ) 

1466 

1467 elif n_name.name in ("startswith", "endswith"): 

1468 return ast.Binary_Expression( 

1469 mh=self.mh, 

1470 location=t_name.location, 

1471 typ=self.builtin_bool, 

1472 operator=( 

1473 ast.Binary_Operator.STRING_STARTSWITH 

1474 if "startswith" in n_name.name 

1475 else ast.Binary_Operator.STRING_ENDSWITH 

1476 ), 

1477 n_lhs=parameters[0], 

1478 n_rhs=parameters[1], 

1479 ) 

1480 

1481 elif n_name.name == "matches": 

1482 parameters[1].ensure_type(self.mh, ast.Builtin_String) 

1483 try: 

1484 # lobster-trace: LRM.Static_Regular_Expression 

1485 # scope is None on purpose to enforce static context 

1486 value = parameters[1].evaluate(self.mh, None, None) 

1487 assert isinstance(value.typ, ast.Builtin_String) 

1488 re.compile(value.value) 

1489 except re.error as err: 

1490 self.mh.error(value.location, str(err)) 

1491 return ast.Binary_Expression( 

1492 mh=self.mh, 

1493 location=t_name.location, 

1494 typ=self.builtin_bool, 

1495 operator=ast.Binary_Operator.STRING_REGEX, 

1496 n_lhs=parameters[0], 

1497 n_rhs=parameters[1], 

1498 ) 

1499 

1500 elif n_name.name == "oneof": 

1501 return ast.OneOf_Expression( 

1502 mh=self.mh, 

1503 location=t_name.location, 

1504 typ=self.builtin_bool, 

1505 choices=parameters, 

1506 ) 

1507 

1508 elif isinstance(n_name, ast.Builtin_Numeric_Type): 

1509 parameters[0].ensure_type(self.mh, ast.Builtin_Numeric_Type) 

1510 if isinstance(n_name, ast.Builtin_Integer): 

1511 return ast.Unary_Expression( 

1512 mh=self.mh, 

1513 location=t_name.location, 

1514 typ=self.builtin_int, 

1515 operator=ast.Unary_Operator.CONVERSION_TO_INT, 

1516 n_operand=parameters[0], 

1517 ) 

1518 elif isinstance(n_name, ast.Builtin_Decimal): 

1519 return ast.Unary_Expression( 

1520 mh=self.mh, 

1521 location=t_name.location, 

1522 typ=self.builtin_decimal, 

1523 operator=ast.Unary_Operator.CONVERSION_TO_DECIMAL, 

1524 n_operand=parameters[0], 

1525 ) 

1526 else: 

1527 self.mh.ice_loc(t_name.location, "unexpected type conversion") 

1528 

1529 else: 

1530 self.mh.ice_loc(t_name.location, "unexpected builtin") 

1531 

1532 def parse_name(self, scope): 

1533 # lobster-trace: LRM.Names 

1534 

1535 # This is a bit more complex. The grammar is: 

1536 # 

1537 # qualified_name ::= [ IDENTIFIER_package_name '.' ] IDENTIFIER_name 

1538 # 

1539 # name ::= qualified_name 

1540 # | name '.' IDENTIFIER 

1541 # | name '[' expression ']' 

1542 # | name '(' parameter_list ')' 

1543 # 

1544 # parameter_list ::= expression { ',' expression } 

1545 

1546 assert isinstance(scope, ast.Scope) 

1547 

1548 # All names start with a (qualified) identifier. We parse that 

1549 # first. There is a special complication for functions, as 

1550 # builtin functions (e.g. len) can shadow record 

1551 # components. However as functions cannot be stored in 

1552 # components the true grammar for function calls is always 

1553 # IDENTIFIER '('; so we can slightly special case this. 

1554 

1555 # lobster-trace: LRM.Builtin_Functions 

1556 # lobster-trace: LRM.Builtin_Type_Conversion_Functions 

1557 self.match("IDENTIFIER") 

1558 if self.peek("BRA"): 

1559 # If we follow our name with brackets 

1560 # immediately, we have a builtin function call. 

1561 n_name = self.stab.lookup(self.mh, self.ct) 

1562 if not isinstance(n_name, (ast.Builtin_Function, ast.Builtin_Numeric_Type)): 1562 ↛ 1563line 1562 didn't jump to line 1563 because the condition on line 1562 was never true

1563 self.mh.error( 

1564 self.ct.location, "not a valid builtin function or numeric type" 

1565 ) 

1566 else: 

1567 n_name = self.parse_qualified_name(scope, match_ident=False) 

1568 

1569 # Enum literals are a bit different, so we deal with them 

1570 # first. 

1571 if isinstance(n_name, ast.Enumeration_Type): 

1572 n_name.set_ast_link(self.ct) 

1573 self.match("DOT") 

1574 n_name.set_ast_link(self.ct) 

1575 self.match("IDENTIFIER") 

1576 lit = n_name.literals.lookup(self.mh, self.ct, ast.Enumeration_Literal_Spec) 

1577 enum_lit = ast.Enumeration_Literal(location=self.ct.location, literal=lit) 

1578 enum_lit.set_ast_link(self.ct) 

1579 return enum_lit 

1580 

1581 # Anything that remains is either a function call or an actual 

1582 # name. Let's just enforce this for sanity. 

1583 if not isinstance( 1583 ↛ 1592line 1583 didn't jump to line 1592 because the condition on line 1583 was never true

1584 n_name, 

1585 ( 

1586 ast.Builtin_Function, 

1587 ast.Builtin_Numeric_Type, 

1588 ast.Composite_Component, 

1589 ast.Quantified_Variable, 

1590 ), 

1591 ): 

1592 self.mh.error( 

1593 self.ct.location, 

1594 "%s %s is not a valid name" % (n_name.__class__.__name__, n_name.name), 

1595 ) 

1596 

1597 # Right now function calls and type conversions must be 

1598 # top-level, so let's get these out of the way as well. 

1599 if isinstance(n_name, (ast.Builtin_Function, ast.Builtin_Numeric_Type)): 

1600 # lobster-trace: LRM.Builtin_Functions 

1601 # lobster-trace: LRM.Builtin_Type_Conversion_Functions 

1602 return self.parse_builtin(scope, n_name, self.ct) 

1603 

1604 assert isinstance(n_name, (ast.Composite_Component, ast.Quantified_Variable)) 

1605 

1606 # We now process the potentially recursive part: 

1607 # | name '.' IDENTIFIER 

1608 # | name '[' expression ']' 

1609 n_name = ast.Name_Reference(location=self.ct.location, entity=n_name) 

1610 n_name.set_ast_link(self.ct) 

1611 while self.peek("DOT") or self.peek("S_BRA"): 

1612 if self.peek("DOT"): 

1613 if not isinstance( 1613 ↛ 1617line 1613 didn't jump to line 1617 because the condition on line 1613 was never true

1614 n_name.typ, (ast.Tuple_Type, ast.Record_Type, ast.Union_Type) 

1615 ): 

1616 # lobster-trace: LRM.Valid_Index_Prefixes 

1617 self.mh.error( 

1618 n_name.location, 

1619 "expression '%s' has type %s, " 

1620 "which is not a tuple, record, or union" 

1621 % (n_name.to_string(), n_name.typ.name), 

1622 ) 

1623 

1624 self.match("DOT") 

1625 t_dot = self.ct 

1626 self.match("IDENTIFIER") 

1627 t_field = self.ct 

1628 

1629 is_union_access = isinstance(n_name.typ, ast.Union_Type) 

1630 is_universal = True 

1631 

1632 if is_union_access: 

1633 # lobster-trace: LRM.Union_Type_Field_Access 

1634 # lobster-trace: LRM.Union_Type_Field_Type_Conflict 

1635 # lobster-trace: LRM.Union_Type_Field_Access_Validity 

1636 field_name = t_field.value 

1637 field_map = n_name.typ.get_field_map() 

1638 if field_name not in field_map: 

1639 self.mh.error( 

1640 t_field.location, 

1641 "field %s does not exist in any member" 

1642 " of union type %s" % (field_name, n_name.typ.name), 

1643 ) 

1644 info = field_map[field_name] 

1645 if info["n_typ"] is None: 

1646 self.mh.error( 

1647 t_field.location, 

1648 "field %s has conflicting types in" 

1649 " members of union type %s" % (field_name, n_name.typ.name), 

1650 ) 

1651 is_universal = info["count"] == info["total"] 

1652 # lobster-trace: LRM.Union_Type_Partial_Field_Access 

1653 if self.lint_mode and not is_universal: 

1654 self.mh.check( 

1655 t_field.location, 

1656 "field %s exists only in %u of %u" 

1657 " members of union type %s;" 

1658 " accessing it on other members" 

1659 " returns null" 

1660 % ( 

1661 field_name, 

1662 info["count"], 

1663 info["total"], 

1664 n_name.typ.name, 

1665 ), 

1666 "union_partial_field_access", 

1667 ) 

1668 n_field = info["component"] 

1669 else: 

1670 n_field = n_name.typ.components.lookup( 

1671 self.mh, t_field, ast.Composite_Component 

1672 ) 

1673 

1674 n_field.set_ast_link(t_field) 

1675 n_name = ast.Field_Access_Expression( 

1676 mh=self.mh, 

1677 location=t_field.location, 

1678 n_prefix=n_name, 

1679 n_field=n_field, 

1680 is_union_access=is_union_access, 

1681 is_universal=is_universal, 

1682 ) 

1683 n_name.set_ast_link(t_dot) 

1684 

1685 elif self.peek("S_BRA"): 1685 ↛ 1611line 1685 didn't jump to line 1611 because the condition on line 1685 was always true

1686 if not isinstance(n_name.typ, ast.Array_Type): 

1687 self.mh.error( 

1688 n_name.location, 

1689 "expression '%s' has type %s, " 

1690 "which is not an array" % (n_name.to_string(), n_name.typ.name), 

1691 ) 

1692 

1693 self.match("S_BRA") 

1694 t_bracket = self.ct 

1695 n_index = self.parse_expression(scope) 

1696 self.match("S_KET") 

1697 a_binary = ast.Binary_Operator.INDEX 

1698 t_bracket.ast_link = a_binary 

1699 self.ct.ast_link = a_binary 

1700 

1701 n_name = ast.Binary_Expression( 

1702 mh=self.mh, 

1703 location=t_bracket.location, 

1704 typ=n_name.typ.element_type, 

1705 operator=a_binary, 

1706 n_lhs=n_name, 

1707 n_rhs=n_index, 

1708 ) 

1709 

1710 return n_name 

1711 

1712 def parse_check_block(self): 

1713 # lobster-trace: LRM.Check_Block 

1714 t_severity = None 

1715 self.match_kw("checks") 

1716 t_checks = self.ct 

1717 self.match("IDENTIFIER") 

1718 # lobster-trace: LRM.Applicable_Types 

1719 # lobster-trace: LRM.Applicable_Components 

1720 n_ctype = self.cu.package.symbols.lookup(self.mh, self.ct, ast.Composite_Type) 

1721 n_check_block = ast.Check_Block(location=self.ct.location, n_typ=n_ctype) 

1722 n_check_block.set_ast_link(t_checks) 

1723 n_ctype.set_ast_link(self.ct) 

1724 scope = ast.Scope() 

1725 scope.push(self.stab) 

1726 scope.push(self.cu.package.symbols) 

1727 scope.push(n_ctype.components) 

1728 self.match("C_BRA") 

1729 n_check_block.set_ast_link(self.ct) 

1730 while not self.peek("C_KET"): 

1731 c_expr = self.parse_expression(scope) 

1732 if not isinstance(c_expr.typ, ast.Builtin_Boolean): 1732 ↛ 1733line 1732 didn't jump to line 1733 because the condition on line 1732 was never true

1733 self.mh.error(c_expr.location, "check expression must be Boolean") 

1734 

1735 self.match("COMMA") 

1736 t_first_comma = self.ct 

1737 if self.peek("KEYWORD"): 

1738 self.match("KEYWORD") 

1739 t_severity = self.ct 

1740 if self.ct.value not in ("warning", "error", "fatal"): 1740 ↛ 1741line 1740 didn't jump to line 1741 because the condition on line 1740 was never true

1741 self.mh.error(self.ct.location, "expected warning|error|fatal") 

1742 c_sev = self.ct.value 

1743 else: 

1744 c_sev = "error" 

1745 

1746 self.match("STRING") 

1747 if "\n" in self.ct.value: 

1748 # lobster-trace: LRM.No_Newlines_In_Message 

1749 self.mh.error( 

1750 self.ct.location, 

1751 "error message must not contain a newline", 

1752 fatal=False, 

1753 ) 

1754 t_msg = self.ct 

1755 

1756 has_extrainfo = False 

1757 has_anchor = False 

1758 if self.peek("COMMA"): 

1759 self.match("COMMA") 

1760 t_second_comma = self.ct 

1761 if self.peek("IDENTIFIER"): 

1762 has_anchor = True 

1763 elif self.peek("STRING"): 1763 ↛ 1766line 1763 didn't jump to line 1766 because the condition on line 1763 was always true

1764 has_extrainfo = True 

1765 else: 

1766 self.mh.error( 

1767 self.nt.location, 

1768 "expected either a details string or" 

1769 " identifier to anchor the check message", 

1770 ) 

1771 

1772 if has_extrainfo: 

1773 self.match("STRING") 

1774 t_extrainfo = self.ct 

1775 c_extrainfo = self.ct.value 

1776 

1777 if self.peek("COMMA"): 1777 ↛ 1778line 1777 didn't jump to line 1778 because the condition on line 1777 was never true

1778 self.match("COMMA") 

1779 t_third_comma = self.ct 

1780 has_anchor = True 

1781 

1782 else: 

1783 c_extrainfo = None 

1784 

1785 if has_anchor: 

1786 self.match("IDENTIFIER") 

1787 t_anchor = self.ct 

1788 c_anchor = n_ctype.components.lookup( 

1789 self.mh, self.ct, ast.Composite_Component 

1790 ) 

1791 else: 

1792 c_anchor = None 

1793 

1794 n_check = ast.Check( 

1795 n_type=n_ctype, 

1796 n_expr=c_expr, 

1797 n_anchor=c_anchor, 

1798 severity=c_sev, 

1799 t_message=t_msg, 

1800 extrainfo=c_extrainfo, 

1801 ) 

1802 

1803 # pylint: disable=possibly-used-before-assignment 

1804 # pylint: disable=used-before-assignment 

1805 

1806 n_check.set_ast_link(t_first_comma) 

1807 if t_severity: 

1808 n_check.set_ast_link(t_severity) 

1809 n_check.set_ast_link(t_msg) 

1810 if c_extrainfo or c_anchor: 

1811 n_check.set_ast_link(t_second_comma) 

1812 if c_extrainfo: 

1813 n_check.set_ast_link(t_extrainfo) 

1814 if c_anchor: 

1815 c_anchor.set_ast_link(t_anchor) 

1816 if c_anchor and c_extrainfo: 1816 ↛ 1817line 1816 didn't jump to line 1817 because the condition on line 1816 was never true

1817 n_check.set_ast_link(t_third_comma) 

1818 

1819 n_ctype.add_check(n_check) 

1820 n_check_block.add_check(n_check) 

1821 

1822 assert scope.size() == 3 

1823 

1824 self.match("C_KET") 

1825 n_check_block.set_ast_link(self.ct) 

1826 

1827 return n_check_block 

1828 

1829 def parse_section_declaration(self): 

1830 # lobster-trace: LRM.Section_Declaration 

1831 self.match_kw("section") 

1832 t_section = self.ct 

1833 self.match("STRING") 

1834 sec = ast.Section( 

1835 name=self.ct.value, 

1836 location=self.ct.location, 

1837 parent=self.section[-1] if self.section else None, 

1838 ) 

1839 sec.set_ast_link(self.ct) 

1840 sec.set_ast_link(t_section) 

1841 self.section.append(sec) 

1842 self.match("C_BRA") 

1843 sec.set_ast_link(self.ct) 

1844 while not self.peek("C_KET"): 

1845 self.parse_trlc_entry() 

1846 self.match("C_KET") 

1847 sec.set_ast_link(self.ct) 

1848 self.section.pop() 

1849 

1850 def parse_boolean(self): 

1851 # lobster-trace: LRM.Boolean_Values 

1852 self.match("KEYWORD") 

1853 if self.ct.value in ("true", "false"): 1853 ↛ 1856line 1853 didn't jump to line 1856 because the condition on line 1853 was always true

1854 return ast.Boolean_Literal(self.ct, self.builtin_bool) 

1855 else: 

1856 self.mh.error(self.ct.location, "expected boolean literal (true or false)") 

1857 

1858 def parse_value(self, typ): 

1859 # lobster-trace: LRM.Tuple_Syntax_Correct_Form 

1860 assert isinstance(typ, ast.Type) 

1861 

1862 if isinstance(typ, ast.Builtin_Numeric_Type): 

1863 # lobster-trace: LRM.Integer_Values 

1864 # lobster-trace: LRM.Decimal_Values 

1865 if self.peek("OPERATOR") and self.nt.value in Parser.ADDING_OPERATOR: 

1866 self.match("OPERATOR") 

1867 t_op = self.ct 

1868 e_op = ( 

1869 ast.Unary_Operator.PLUS 

1870 if t_op.value == "+" 

1871 else ast.Unary_Operator.MINUS 

1872 ) 

1873 t_op.ast_link = e_op 

1874 else: 

1875 t_op = None 

1876 

1877 if isinstance(typ, ast.Builtin_Decimal): 

1878 self.match("DECIMAL") 

1879 rv = ast.Decimal_Literal(self.ct, self.builtin_decimal) 

1880 rv.set_ast_link(self.ct) 

1881 elif isinstance(typ, ast.Builtin_Integer): 

1882 self.match("INTEGER") 

1883 rv = ast.Integer_Literal(self.ct, self.builtin_int) 

1884 rv.set_ast_link(self.ct) 

1885 else: 

1886 assert False 

1887 

1888 if t_op: 

1889 rv = ast.Unary_Expression( 

1890 mh=self.mh, 

1891 location=t_op.location, 

1892 typ=rv.typ, 

1893 operator=e_op, 

1894 n_operand=rv, 

1895 ) 

1896 

1897 return rv 

1898 

1899 elif isinstance(typ, ast.Builtin_Markup_String): 

1900 # lobster-trace: LRM.Markup_String_Values 

1901 return self.parse_markup_string() 

1902 

1903 elif isinstance(typ, ast.Builtin_String): 

1904 # lobster-trace: LRM.String_Values 

1905 self.match("STRING") 

1906 rv = ast.String_Literal(self.ct, self.builtin_str) 

1907 rv.set_ast_link(self.ct) 

1908 return rv 

1909 

1910 elif isinstance(typ, ast.Builtin_Boolean): 

1911 rv = self.parse_boolean() 

1912 rv.set_ast_link(self.ct) 

1913 return rv 

1914 

1915 elif isinstance(typ, ast.Array_Type): 

1916 self.match("S_BRA") 

1917 rv = ast.Array_Aggregate(self.ct.location, typ) 

1918 rv.set_ast_link(self.ct) 

1919 while not self.peek("S_KET"): 

1920 array_elem = self.parse_value(typ.element_type) 

1921 rv.append(array_elem) 

1922 if self.peek("COMMA"): 

1923 self.match("COMMA") 

1924 rv.set_ast_link(self.ct) 

1925 elif self.peek("S_KET") or self.nt is None: 1925 ↛ 1928line 1925 didn't jump to line 1928 because the condition on line 1925 was always true

1926 break 

1927 else: 

1928 self.mh.error( 

1929 self.ct.location, 

1930 "comma separating array elements is missing", 

1931 fatal=False, 

1932 ) 

1933 

1934 self.match("S_KET") 

1935 rv.set_ast_link(self.ct) 

1936 

1937 if len(rv.value) < typ.lower_bound: 

1938 self.mh.error( 

1939 self.ct.location, 

1940 "this array requires at least %u elements " 

1941 "(only %u provided)" % (typ.lower_bound, len(rv.value)), 

1942 fatal=False, 

1943 ) 

1944 if typ.upper_bound and len(rv.value) > typ.upper_bound: 

1945 self.mh.error( 

1946 rv.value[typ.upper_bound].location, 

1947 "this array requires at most %u elements " 

1948 "(%u provided)" % (typ.upper_bound, len(rv.value)), 

1949 fatal=False, 

1950 ) 

1951 

1952 return rv 

1953 

1954 elif isinstance(typ, ast.Enumeration_Type): 

1955 enum = self.parse_qualified_name(self.default_scope, ast.Enumeration_Type) 

1956 enum.set_ast_link(self.ct) 

1957 if enum != typ: 

1958 self.mh.error(self.ct.location, "expected %s" % typ.name) 

1959 self.match("DOT") 

1960 enum.set_ast_link(self.ct) 

1961 self.match("IDENTIFIER") 

1962 lit = enum.literals.lookup(self.mh, self.ct, ast.Enumeration_Literal_Spec) 

1963 return ast.Enumeration_Literal(self.ct.location, lit) 

1964 

1965 elif isinstance(typ, (ast.Record_Type, ast.Union_Type)): 

1966 self.match("IDENTIFIER") 

1967 t_name = self.ct 

1968 if self.peek("DOT"): 

1969 # Parse a (potentially nested) package prefix followed by 

1970 # the object name via a greedy sub-package descent. 

1971 the_pkg = self.stab.lookup(self.mh, t_name, ast.Package) 

1972 the_pkg.set_ast_link(t_name) 

1973 the_pkg, t_name = self.descend_sub_packages(the_pkg, t_name) 

1974 else: 

1975 the_pkg = self.cu.package 

1976 

1977 rv = ast.Record_Reference( 

1978 location=t_name.location, name=t_name.value, typ=typ, package=the_pkg 

1979 ) 

1980 rv.set_ast_link(t_name) 

1981 

1982 # We can do an early lookup if the target is known 

1983 if the_pkg.symbols.contains(t_name.value): 

1984 rv.resolve_references(self.mh) 

1985 

1986 return rv 

1987 

1988 elif isinstance(typ, ast.Tuple_Type) and typ.has_separators(): 

1989 # lobster-trace: LRM.Tuple_Separator_Form 

1990 rv = ast.Tuple_Aggregate(self.nt.location, typ) 

1991 

1992 next_is_optional = False 

1993 for n_item in typ.iter_sequence(): 

1994 if isinstance(n_item, ast.Composite_Component): 

1995 if next_is_optional and n_item.optional: 

1996 break 

1997 value = self.parse_value(n_item.n_typ) 

1998 rv.assign(n_item.name, value) 

1999 

2000 elif n_item.token.kind in ("AT", "COLON", "SEMICOLON"): 

2001 if self.peek(n_item.token.kind): 

2002 self.match(n_item.token.kind) 

2003 n_item.set_ast_link(self.ct) 

2004 else: 

2005 next_is_optional = True 

2006 

2007 elif n_item.token.kind == "IDENTIFIER": 

2008 if self.peek("IDENTIFIER") and self.nt.value == n_item.token.value: 

2009 self.match("IDENTIFIER") 

2010 n_item.set_ast_link(self.ct) 

2011 else: 

2012 next_is_optional = True 

2013 

2014 else: 

2015 assert False 

2016 

2017 return rv 

2018 

2019 elif isinstance(typ, ast.Tuple_Type) and not typ.has_separators(): 

2020 # lobster-trace: LRM.Tuple_Generic_Form 

2021 self.match("BRA") 

2022 rv = ast.Tuple_Aggregate(self.ct.location, typ) 

2023 rv.set_ast_link(self.ct) 

2024 

2025 first = True 

2026 for n_field in typ.iter_sequence(): 

2027 if first: 

2028 first = False 

2029 else: 

2030 self.match("COMMA") 

2031 rv.set_ast_link(self.ct) 

2032 rv.assign(n_field.name, self.parse_value(n_field.n_typ)) 

2033 

2034 self.match("KET") 

2035 rv.set_ast_link(self.ct) 

2036 return rv 

2037 

2038 else: 

2039 self.mh.ice_loc( 

2040 self.ct.location, 

2041 "logic error: unexpected type %s" % typ.__class__.__name__, 

2042 ) 

2043 

2044 def parse_markup_string(self): 

2045 # lobster-trace: LRM.Markup_String_Values 

2046 self.match("STRING") 

2047 rv = ast.String_Literal(self.ct, self.builtin_mstr) 

2048 mpar = Markup_Parser(self, rv) 

2049 mpar.parse_all_references() 

2050 return rv 

2051 

2052 def parse_record_object_declaration(self): 

2053 # lobster-trace: LRM.Section_Declaration 

2054 # lobster-trace: LRM.Record_Object_Declaration 

2055 # lobster-trace: LRM.Valid_Record_Types 

2056 # lobster-trace: LRM.Valid_Components 

2057 # lobster-trace: LRM.Valid_Enumeration_Literals 

2058 # lobster-trace: LRM.Mandatory_Components 

2059 # lobster-trace: LRM.Evaluation_Of_Checks 

2060 # lobster-trace: LRM.Single_Value_Assignment 

2061 

2062 r_typ = self.parse_qualified_name(self.default_scope, ast.Record_Type) 

2063 r_typ.set_ast_link(self.ct) 

2064 # lobster-trace: LRM.Abstract_Types 

2065 if r_typ.is_abstract: 

2066 self.mh.error( 

2067 self.ct.location, 

2068 "cannot declare object of abstract record type %s" % r_typ.name, 

2069 ) 

2070 

2071 self.match("IDENTIFIER") 

2072 obj = ast.Record_Object( 

2073 name=self.ct.value, 

2074 location=self.ct.location, 

2075 n_typ=r_typ, 

2076 section=self.section.copy() if self.section else None, 

2077 n_package=self.cu.package, 

2078 ) 

2079 self.cu.package.symbols.register(self.mh, obj) 

2080 obj.set_ast_link(self.ct) 

2081 

2082 self.match("C_BRA") 

2083 obj.set_ast_link(self.ct) 

2084 while not self.peek("C_KET"): 

2085 self.match("IDENTIFIER") 

2086 comp = r_typ.components.lookup(self.mh, self.ct, ast.Composite_Component) 

2087 if obj.is_component_implicit_null(comp): 

2088 self.mh.error( 

2089 self.ct.location, 

2090 "component '%s' already assigned at line %i" 

2091 % (comp.name, obj.field[comp.name].location.line_no), 

2092 ) 

2093 comp.set_ast_link(self.ct) 

2094 if r_typ.is_frozen(comp): 

2095 self.mh.error( 

2096 self.ct.location, "cannot overwrite frozen component %s" % comp.name 

2097 ) 

2098 self.match("ASSIGN") 

2099 comp.set_ast_link(self.ct) 

2100 value = self.parse_value(comp.n_typ) 

2101 if not self.ct.ast_link: 

2102 value.set_ast_link(self.ct) 

2103 obj.assign(comp, value) 

2104 

2105 # Check that each non-optional component has been specified 

2106 for comp in r_typ.all_components(): 

2107 if isinstance(obj.field[comp.name], ast.Implicit_Null): 

2108 if r_typ.is_frozen(comp): 

2109 obj.assign(comp, r_typ.get_freezing_expression(comp)) 

2110 elif not comp.optional: 

2111 self.mh.error( 

2112 obj.location, 

2113 "required component %s (see %s) is not defined" 

2114 % (comp.name, self.mh.cross_file_reference(comp.location)), 

2115 ) 

2116 

2117 self.match("C_KET") 

2118 obj.set_ast_link(self.ct) 

2119 

2120 return obj 

2121 

2122 def parse_trlc_entry(self): 

2123 # lobster-trace: LRM.TRLC_File 

2124 if self.peek_kw("section"): 

2125 self.parse_section_declaration() 

2126 else: 

2127 self.cu.add_item(self.parse_record_object_declaration()) 

2128 

2129 def parse_preamble(self, kind): 

2130 assert kind in ("rsl", "trlc") 

2131 # lobster-trace: LRM.Layout 

2132 # lobster-trace: LRM.Preamble 

2133 

2134 self.file_kind = kind 

2135 

2136 # First, parse package indication, declaring the package if 

2137 # needed 

2138 self.match_kw(self.PACKAGE_KEYWORD) 

2139 t_pkg = self.ct 

2140 pkg_name, pkg_location, pkg_tokens = self.parse_dotted_name() 

2141 

2142 if kind == "rsl": 

2143 declare_package = True 

2144 else: 

2145 # lobster-trace: LRM.Late_Package_Declarations 

2146 declare_package = not self.stab.contains(pkg_name) 

2147 

2148 if declare_package: 

2149 # lobster-trace: LRM.Package_Declaration 

2150 pkg = ast.Package( 

2151 name=pkg_name, 

2152 location=pkg_location, 

2153 builtin_stab=self.stab, 

2154 declared_late=kind == "trlc", 

2155 ) 

2156 self.stab.register(self.mh, pkg) 

2157 else: 

2158 pkg = self.stab.lookup_direct(self.mh, pkg_name, pkg_location, ast.Package) 

2159 

2160 pkg.set_ast_link(t_pkg) 

2161 for t in pkg_tokens: 

2162 pkg.set_ast_link(t) 

2163 

2164 # lobster-trace: LRM.Current_Package 

2165 self.cu.set_package(pkg) 

2166 

2167 self.default_scope.push(self.cu.package.symbols) 

2168 

2169 # Second, parse import list (but don't resolve names yet) 

2170 # lobster-trace: LRM.Import_Visibility 

2171 if kind != "check": 2171 ↛ exitline 2171 didn't return from function 'parse_preamble' because the condition on line 2171 was always true

2172 while self.peek_kw("import"): 

2173 self.match_kw("import") 

2174 t_import_kw = self.ct 

2175 imp_name, imp_location, imp_tokens, imp_wildcard = ( 

2176 self.parse_import_name() 

2177 ) 

2178 pkg.set_ast_link(t_import_kw) 

2179 self.cu.add_import( 

2180 self.mh, imp_name, imp_location, imp_wildcard, imp_tokens 

2181 ) 

2182 

2183 def parse_rsl_file(self): 

2184 # lobster-trace: LRM.RSL_File 

2185 assert self.cu.package is not None 

2186 

2187 ok = True 

2188 while not self.peek_eof(): 

2189 try: 

2190 if self.peek_kw("checks"): 

2191 self.cu.add_item(self.parse_check_block()) 

2192 else: 

2193 self.cu.add_item(self.parse_type_declaration()) 

2194 except TRLC_Error as err: 

2195 if not self.error_recovery or err.kind == "lex error": 

2196 raise 

2197 

2198 ok = False 

2199 

2200 # Recovery strategy is to scan until we get the next 

2201 # relevant keyword 

2202 self.skip_until_newline() 

2203 while not self.peek_eof(): 

2204 if ( 

2205 self.peek_kw("checks") 

2206 or self.peek_kw("type") 

2207 or self.peek_kw("abstract") 

2208 or self.peek_kw("final") 

2209 or self.peek_kw("tuple") 

2210 or self.peek_kw("enum") 

2211 ): 

2212 break 

2213 self.advance() 

2214 self.skip_until_newline() 

2215 

2216 self.match_eof() 

2217 

2218 for tok in self.lexer.tokens: 

2219 if tok.kind == "COMMENT": 

2220 self.cu.package.set_ast_link(tok) 

2221 

2222 return ok 

2223 

2224 def parse_trlc_file(self): 

2225 # lobster-trace: LRM.TRLC_File 

2226 assert self.cu.package is not None 

2227 

2228 ok = True 

2229 

2230 while self.peek_kw("section") or self.peek("IDENTIFIER"): 

2231 try: 

2232 self.parse_trlc_entry() 

2233 except TRLC_Error as err: 

2234 if not self.error_recovery or err.kind == "lex error": 

2235 raise 

2236 

2237 ok = False 

2238 

2239 # Recovery strategy is to keep going until we find an 

2240 # identifier that is a package or type, or section, or 

2241 # EOF 

2242 self.skip_until_newline() 

2243 while not self.peek_eof(): 

2244 if self.peek_kw("section"): 2244 ↛ 2245line 2244 didn't jump to line 2245 because the condition on line 2244 was never true

2245 break 

2246 elif not self.peek("IDENTIFIER"): 

2247 pass 

2248 elif self.stab.contains(self.nt.value): 2248 ↛ 2249line 2248 didn't jump to line 2249 because the condition on line 2248 was never true

2249 n_sym = self.stab.lookup_assuming(self.mh, self.nt.value) 

2250 if isinstance(n_sym, ast.Package): 

2251 break 

2252 elif self.cu.package.symbols.contains(self.nt.value): 

2253 n_sym = self.cu.package.symbols.lookup_assuming( 

2254 self.mh, self.nt.value 

2255 ) 

2256 if isinstance(n_sym, ast.Record_Type): 

2257 break 

2258 self.advance() 

2259 self.skip_until_newline() 

2260 

2261 self.match_eof() 

2262 

2263 for tok in self.lexer.tokens: 

2264 if tok.kind == "COMMENT": 

2265 self.cu.package.set_ast_link(tok) 

2266 

2267 return ok