Coverage for trlc/parser.py: 96%

1079 statements  

« prev     ^ index     » next       coverage.py v7.7.1, created at 2025-03-27 00:52 +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# 

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

7# 

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

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

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

11# (at your option) any later version. 

12# 

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

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

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

16# License for more details. 

17# 

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

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

20 

21import re 

22 

23from trlc.nested import Nested_Lexer 

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

25from trlc.errors import Message_Handler, TRLC_Error 

26from trlc import ast 

27 

28 

29class Markup_Token(Token_Base): 

30 # lobster-trace: LRM.Markup_String_Format 

31 

32 KIND = { 

33 "CHARACTER" : "character", 

34 "REFLIST_BEGIN" : "[[", 

35 "REFLIST_END" : "]]", 

36 "REFLIST_COMMA" : ",", 

37 "REFLIST_DOT" : ".", 

38 "REFLIST_IDENTIFIER" : "identifier", 

39 } 

40 

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

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

43 assert isinstance(value, str) 

44 

45 

46class Markup_Lexer(Nested_Lexer): 

47 def __init__(self, mh, literal): 

48 super().__init__(mh, literal) 

49 

50 self.in_reflist = False 

51 

52 def file_location(self): 

53 return self.origin_location 

54 

55 def token(self): 

56 # lobster-trace: LRM.Markup_String_Errors 

57 

58 if self.in_reflist: 

59 self.skip_whitespace() 

60 else: 

61 self.advance() 

62 if self.cc is None: 

63 return None 

64 

65 start_pos = self.lexpos 

66 start_line = self.line_no 

67 start_col = self.col_no 

68 

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

70 kind = "REFLIST_BEGIN" 

71 self.advance() 

72 if self.in_reflist: 

73 self.mh.lex_error(self.source_location(start_line, 

74 start_col, 

75 start_pos, 

76 start_pos + 1), 

77 "cannot nest reference lists") 

78 else: 

79 self.in_reflist = True 

80 

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

82 kind = "REFLIST_END" 

83 self.advance() 

84 if self.in_reflist: 

85 self.in_reflist = False 

86 else: 

87 self.mh.lex_error(self.source_location(start_line, 

88 start_col, 

89 start_pos, 

90 start_pos + 1), 

91 "opening [[ for this ]] found") 

92 

93 elif not self.in_reflist: 

94 kind = "CHARACTER" 

95 

96 elif self.cc == ",": 

97 kind = "REFLIST_COMMA" 

98 

99 elif self.cc == ".": 

100 kind = "REFLIST_DOT" 

101 

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

103 kind = "REFLIST_IDENTIFIER" 

104 while self.nc and (self.is_alnum(self.nc) or 

105 self.nc == "_"): 

106 self.advance() 

107 

108 else: 

109 self.mh.lex_error(self.source_location(start_line, 

110 start_col, 

111 start_pos, 

112 start_pos), 

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

114 

115 loc = self.source_location(start_line, 

116 start_col, 

117 start_pos, 

118 self.lexpos) 

119 

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

121 return Markup_Token(loc, 

122 kind, 

123 self.content[start_pos:self.lexpos + 1]) 

124 

125 

126class Parser_Base: 

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

128 assert isinstance(mh, Message_Handler) 

129 assert isinstance(lexer, Lexer_Base) 

130 assert isinstance(eoc_name, str) 

131 assert isinstance(token_map, dict) 

132 assert isinstance(keywords, frozenset) 

133 self.mh = mh 

134 self.lexer = lexer 

135 

136 self.eoc_name = eoc_name 

137 self.language_tokens = token_map 

138 self.language_keywords = keywords 

139 

140 self.ct = None 

141 self.nt = None 

142 self.advance() 

143 

144 def advance(self): 

145 # lobster-trace: LRM.Comments 

146 self.ct = self.nt 

147 while True: 

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

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

150 break 

151 

152 def skip_until_newline(self): 

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

154 return 

155 current_line = self.ct.location.line_no 

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

157 self.advance() 

158 

159 def peek(self, kind): 

160 assert kind in self.language_tokens, \ 

161 "%s is not a valid token" % kind 

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

163 

164 def peek_eof(self): 

165 return self.nt is None 

166 

167 def peek_kw(self, value): 

168 assert value in self.language_keywords, \ 

169 "%s is not a valid keyword" % value 

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

171 

172 def match(self, kind): 

173 # lobster-trace: LRM.Matching_Value_Types 

174 

175 assert kind in self.language_tokens, \ 

176 "%s is not a valid token" % kind 

177 if self.nt is None: 

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

179 self.mh.error(self.lexer.file_location(), 

180 "expected %s, encountered %s instead" % 

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

182 else: 

183 self.mh.error(self.ct.location, 

184 "expected %s, encountered %s instead" % 

185 (self.language_tokens[kind], self.eoc_name)) 

186 elif self.nt.kind != kind: 

187 self.mh.error(self.nt.location, 

188 "expected %s, encountered %s instead" % 

189 (self.language_tokens[kind], 

190 self.language_tokens[self.nt.kind])) 

191 self.advance() 

192 

193 def match_eof(self): 

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

195 self.mh.error(self.nt.location, 

196 "expected %s, encountered %s instead" % 

197 (self.eoc_name, 

198 self.language_tokens[self.nt.kind])) 

199 

200 def match_kw(self, value): 

201 assert value in self.language_keywords, \ 

202 "%s is not a valid keyword" % value 

203 if self.nt is None: 

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

205 self.mh.error(self.lexer.file_location(), 

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

207 (value, self.eoc_name)) 

208 else: 

209 self.mh.error(self.ct.location, 

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

211 (value, self.eoc_name)) 

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

213 self.mh.error(self.nt.location, 

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

215 (value, 

216 self.language_tokens[self.nt.kind])) 

217 elif self.nt.value != value: 

218 self.mh.error(self.nt.location, 

219 "expected keyword %s," 

220 " encountered keyword %s instead" % 

221 (value, self.nt.value)) 

222 self.advance() 

223 

224 

225class Markup_Parser(Parser_Base): 

226 def __init__(self, parent, literal): 

227 assert isinstance(parent, Parser) 

228 super().__init__(parent.mh, Markup_Lexer(parent.mh, literal), 

229 eoc_name = "end-of-string", 

230 token_map = Markup_Token.KIND, 

231 keywords = frozenset()) 

232 self.parent = parent 

233 self.references = literal.references 

234 

235 def parse_all_references(self): 

236 while self.nt: 

237 if self.peek("CHARACTER"): 

238 self.advance() 

239 else: 

240 self.parse_ref_list() 

241 self.match_eof() 

242 return self.references 

243 

244 def parse_ref_list(self): 

245 self.match("REFLIST_BEGIN") 

246 self.parse_qualified_name() 

247 while self.peek("REFLIST_COMMA"): 

248 self.match("REFLIST_COMMA") 

249 self.parse_qualified_name() 

250 self.match("REFLIST_END") 

251 

252 def parse_qualified_name(self): 

253 # lobster-trace: LRM.Qualified_Name 

254 # lobster-trace: LRM.Valid_Qualifier 

255 # lobster-trace: LRM.Valid_Name 

256 # lobster-trace: LRM.Markup_String_Resolution 

257 # lobster-trace: LRM.Markup_String_Types 

258 

259 self.match("REFLIST_IDENTIFIER") 

260 if self.peek("REFLIST_DOT"): 

261 package = self.parent.stab.lookup_direct( 

262 mh = self.mh, 

263 name = self.ct.value, 

264 error_location = self.ct.location, 

265 required_subclass = ast.Package) 

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

267 self.mh.error(self.ct.location, 

268 "package must be imported before use") 

269 

270 self.match("REFLIST_DOT") 

271 self.match("REFLIST_IDENTIFIER") 

272 else: 

273 package = self.parent.cu.package 

274 

275 ref = ast.Record_Reference(location = self.ct.location, 

276 name = self.ct.value, 

277 typ = None, 

278 package = package) 

279 self.references.append(ref) 

280 

281 

282class Parser(Parser_Base): 

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

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

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

286 

287 def __init__(self, 

288 mh, 

289 stab, 

290 file_name, 

291 lint_mode, 

292 error_recovery, 

293 primary_file=True, 

294 lexer=None): 

295 assert isinstance(mh, Message_Handler) 

296 assert isinstance(stab, ast.Symbol_Table) 

297 assert isinstance(file_name, str) 

298 assert isinstance(lint_mode, bool) 

299 assert isinstance(error_recovery, bool) 

300 assert isinstance(primary_file, bool) 

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

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

303 super().__init__(mh, lexer, 

304 eoc_name = "end-of-file", 

305 token_map = Token.KIND, 

306 keywords = TRLC_Lexer.KEYWORDS) 

307 else: 

308 super().__init__(mh, TRLC_Lexer(mh, file_name), 

309 eoc_name = "end-of-file", 

310 token_map = Token.KIND, 

311 keywords = TRLC_Lexer.KEYWORDS) 

312 

313 self.lint_mode = lint_mode 

314 self.error_recovery = error_recovery 

315 

316 self.stab = stab 

317 self.cu = ast.Compilation_Unit(file_name) 

318 

319 self.primary = primary_file 

320 self.secondary = False 

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

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

323 # was selected by dependency analysis. 

324 

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

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

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

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

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

330 

331 self.section = [] 

332 self.default_scope = ast.Scope() 

333 self.default_scope.push(self.stab) 

334 

335 def parse_described_name(self): 

336 # lobster-trace: LRM.Described_Names 

337 # lobster-trace: LRM.Described_Name_Description 

338 self.match("IDENTIFIER") 

339 name = self.ct 

340 

341 if self.peek("STRING"): 

342 self.match("STRING") 

343 t_descr = self.ct 

344 return name, t_descr.value, t_descr 

345 else: 

346 return name, None, None 

347 

348 def parse_qualified_name(self, 

349 scope, 

350 required_subclass=None, 

351 match_ident=True): 

352 # lobster-trace: LRM.Qualified_Name 

353 # lobster-trace: LRM.Valid_Qualifier 

354 # lobster-trace: LRM.Valid_Name 

355 assert isinstance(scope, ast.Scope) 

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

357 assert isinstance(match_ident, bool) 

358 

359 if match_ident: 

360 self.match("IDENTIFIER") 

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

362 sym.set_ast_link(self.ct) 

363 

364 if isinstance(sym, ast.Package): 

365 if not self.cu.is_visible(sym): 

366 self.mh.error(self.ct.location, 

367 "package must be imported before use") 

368 self.match("DOT") 

369 sym.set_ast_link(self.ct) 

370 self.match("IDENTIFIER") 

371 return sym.symbols.lookup(self.mh, self.ct, required_subclass) 

372 else: 

373 # Easiest way to generate the correct error message 

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

375 

376 def parse_type_declaration(self): 

377 # lobster-trace: LRM.Type_Declarations 

378 if self.peek_kw("enum"): 

379 n_item = self.parse_enum_declaration() 

380 elif self.peek_kw("tuple"): 

381 n_item = self.parse_tuple_declaration() 

382 else: 

383 n_item = self.parse_record_declaration() 

384 assert isinstance(n_item, ast.Concrete_Type) 

385 return n_item 

386 

387 def parse_enum_declaration(self): 

388 # lobster-trace: LRM.Enumeration_Declaration 

389 self.match_kw("enum") 

390 t_enum = self.ct 

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

392 

393 enum = ast.Enumeration_Type(name = name.value, 

394 description = description, 

395 location = name.location, 

396 package = self.cu.package) 

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

398 enum.set_ast_link(t_enum) 

399 enum.set_ast_link(name) 

400 if t_description: 

401 enum.set_ast_link(t_description) 

402 

403 self.match("C_BRA") 

404 enum.set_ast_link(self.ct) 

405 empty = True 

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

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

408 lit = ast.Enumeration_Literal_Spec(name = name.value, 

409 description = description, 

410 location = name.location, 

411 enum = enum) 

412 lit.set_ast_link(name) 

413 if t_description: 

414 lit.set_ast_link(self.ct) 

415 empty = False 

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

417 self.match("C_KET") 

418 enum.set_ast_link(self.ct) 

419 

420 if empty: 

421 # lobster-trace: LRM.No_Empty_Enumerations 

422 self.mh.error(enum.location, 

423 "empty enumerations are not permitted") 

424 

425 return enum 

426 

427 def parse_tuple_field(self, 

428 n_tuple, 

429 optional_allowed, 

430 optional_reason, 

431 optional_required): 

432 assert isinstance(n_tuple, ast.Tuple_Type) 

433 assert isinstance(optional_allowed, bool) 

434 assert isinstance(optional_reason, str) 

435 assert isinstance(optional_required, bool) 

436 assert optional_allowed or not optional_required 

437 

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

439 

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

441 self.match_kw("optional") 

442 t_optional = self.ct 

443 field_is_optional = True 

444 if not optional_allowed: 

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

446 else: 

447 field_is_optional = False 

448 t_optional = None 

449 

450 # lobster-trace: LRM.Tuple_Field_Types 

451 field_type = self.parse_qualified_name(self.default_scope, 

452 ast.Type) 

453 comp = ast.Composite_Component(name = field_name.value, 

454 description = field_description, 

455 location = field_name.location, 

456 member_of = n_tuple, 

457 n_typ = field_type, 

458 optional = field_is_optional) 

459 comp.set_ast_link(field_name) 

460 if t_descr: 

461 comp.set_ast_link(t_descr) 

462 if field_is_optional: 

463 comp.set_ast_link(t_optional) 

464 

465 return comp 

466 

467 def parse_tuple_declaration(self): 

468 # lobster-trace: LRM.Tuple_Declaration 

469 self.match_kw("tuple") 

470 t_tuple = self.ct 

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

472 

473 n_tuple = ast.Tuple_Type(name = name.value, 

474 description = description, 

475 location = name.location, 

476 package = self.cu.package) 

477 

478 n_tuple.set_ast_link(t_tuple) 

479 n_tuple.set_ast_link(name) 

480 if t_descr: 

481 n_tuple.set_ast_link(t_descr) 

482 self.match("C_BRA") 

483 n_tuple.set_ast_link(self.ct) 

484 

485 n_field = self.parse_tuple_field( 

486 n_tuple, 

487 optional_allowed = False, 

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

489 optional_required = False) 

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

491 

492 has_separators = False 

493 optional_required = False 

494 separator_allowed = True 

495 

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

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

498 has_separators = True 

499 self.match_kw("separator") 

500 t_sep = self.ct 

501 if not separator_allowed: 

502 # lobster-trace: LRM.Tuple_Separators_All_Or_None 

503 self.mh.error(self.ct.location, 

504 "either all fields must be separated," 

505 " or none") 

506 if self.peek("IDENTIFIER") or \ 506 ↛ 518line 506 didn't jump to line 518 because the condition on line 506 was always true

507 self.peek("AT") or \ 

508 self.peek("COLON") or \ 

509 self.peek("SEMICOLON"): 

510 self.advance() 

511 sep = ast.Separator(self.ct) 

512 sep.set_ast_link(t_sep) 

513 sep.set_ast_link(self.ct) 

514 n_tuple.add_separator(sep) 

515 else: 

516 separator_allowed = False 

517 # lobster-trace: LRM.Tuple_Optional_Requires_Separators 

518 n_field = self.parse_tuple_field( 

519 n_tuple, 

520 optional_allowed = has_separators, 

521 optional_reason = ("optional only permitted in tuples" 

522 " with separators"), 

523 optional_required = optional_required) 

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

525 # lobster-trace: LRM.Tuple_Optional_Fields 

526 optional_required |= n_field.optional 

527 

528 self.match("C_KET") 

529 n_tuple.set_ast_link(self.ct) 

530 

531 # Final check to ban tuples with separators containing other 

532 # tuples. 

533 if has_separators: 

534 # lobster-trace: LRM.Restricted_Tuple_Nesting 

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

536 if isinstance(n_field.n_typ, ast.Tuple_Type) and \ 

537 n_field.n_typ.has_separators(): 

538 self.mh.error( 

539 n_field.location, 

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

541 " may not contain another tuple with separators" 

542 % n_tuple.name) 

543 

544 # Late registration to avoid recursion in tuples 

545 # lobster-trace: LRM.Tuple_Field_Types 

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

547 

548 return n_tuple 

549 

550 def parse_record_component(self, n_record): 

551 assert isinstance(n_record, ast.Record_Type) 

552 

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

554 t_optional = None 

555 c_optional = False 

556 if self.peek_kw("optional"): 

557 self.match_kw("optional") 

558 t_optional = self.ct 

559 c_optional = True 

560 c_typ = self.parse_qualified_name(self.default_scope, 

561 ast.Type) 

562 c_typ.set_ast_link(self.ct) 

563 

564 if self.peek("S_BRA"): 

565 self.match("S_BRA") 

566 t_s_bra = self.ct 

567 self.match("INTEGER") 

568 t_lo = self.ct 

569 a_lo = self.ct.value 

570 loc_lo = self.ct.location 

571 self.match("RANGE") 

572 t_range = self.ct 

573 a_loc = self.ct.location 

574 a_hi = None 

575 if self.peek("INTEGER"): 

576 self.match("INTEGER") 

577 a_hi = self.ct.value 

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

579 self.match("OPERATOR") 

580 else: 

581 self.mh.error(self.nt.location, 

582 "expected INTEGER or * for upper bound") 

583 t_hi = self.ct 

584 loc_hi = self.ct.location 

585 self.match("S_KET") 

586 t_s_ket = self.ct 

587 c_typ = ast.Array_Type(location = a_loc, 

588 element_type = c_typ, 

589 lower_bound = a_lo, 

590 upper_bound = a_hi, 

591 loc_lower = loc_lo, 

592 loc_upper = loc_hi) 

593 c_typ.set_ast_link(t_s_bra) 

594 c_typ.set_ast_link(t_lo) 

595 c_typ.set_ast_link(t_range) 

596 c_typ.set_ast_link(t_hi) 

597 c_typ.set_ast_link(t_s_ket) 

598 

599 c_comp = ast.Composite_Component(name = c_name.value, 

600 description = c_descr, 

601 location = c_name.location, 

602 member_of = n_record, 

603 n_typ = c_typ, 

604 optional = c_optional) 

605 c_comp.set_ast_link(c_name) 

606 if t_descr: 

607 c_comp.set_ast_link(t_descr) 

608 if c_optional: 

609 c_comp.set_ast_link(t_optional) 

610 

611 return c_comp 

612 

613 def parse_record_declaration(self): 

614 t_abstract = None 

615 t_final = None 

616 is_abstract = False 

617 is_final = False 

618 if self.peek_kw("abstract"): 

619 self.match_kw("abstract") 

620 t_abstract = self.ct 

621 is_abstract = True 

622 elif self.peek_kw("final"): 

623 self.match_kw("final") 

624 t_final = self.ct 

625 is_final = True 

626 

627 self.match_kw("type") 

628 t_type = self.ct 

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

630 

631 if self.peek_kw("extends"): 

632 self.match_kw("extends") 

633 t_extends = self.ct 

634 root_record = self.parse_qualified_name(self.default_scope, 

635 ast.Record_Type) 

636 root_record.set_ast_link(t_extends) 

637 root_record.set_ast_link(self.ct) 

638 else: 

639 root_record = None 

640 

641 if self.lint_mode and \ 

642 root_record and root_record.is_final and \ 

643 not is_final: 

644 self.mh.check(name.location, 

645 "consider clarifying that this record is final", 

646 "clarify_final", 

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

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

649 "clarifies this to casual readers." % 

650 root_record.fully_qualified_name())) 

651 

652 record = ast.Record_Type(name = name.value, 

653 description = description, 

654 location = name.location, 

655 package = self.cu.package, 

656 n_parent = root_record, 

657 is_abstract = is_abstract) 

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

659 if is_abstract: 

660 record.set_ast_link(t_abstract) 

661 if is_final: 

662 record.set_ast_link(t_final) 

663 record.set_ast_link(t_type) 

664 record.set_ast_link(name) 

665 if t_description: 

666 record.set_ast_link(t_description) 

667 

668 self.match("C_BRA") 

669 record.set_ast_link(self.ct) 

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

671 if self.peek_kw("freeze"): 

672 self.match_kw("freeze") 

673 t_freeze = self.ct 

674 self.match("IDENTIFIER") 

675 n_comp = record.components.lookup(self.mh, 

676 self.ct, 

677 ast.Composite_Component) 

678 if record.is_frozen(n_comp): 

679 n_value = record.get_freezing_expression(n_comp) 

680 self.mh.error( 

681 self.ct.location, 

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

683 (n_comp.name, 

684 self.mh.cross_file_reference(n_value.location))) 

685 n_comp.set_ast_link(t_freeze) 

686 n_comp.set_ast_link(self.ct) 

687 self.match("ASSIGN") 

688 n_comp.set_ast_link(self.ct) 

689 n_value = self.parse_value(n_comp.n_typ) 

690 n_value.set_ast_link(self.ct) 

691 

692 record.frozen[n_comp.name] = n_value 

693 

694 else: 

695 n_comp = self.parse_record_component(record) 

696 if record.is_final: 

697 self.mh.error(n_comp.location, 

698 "cannot declare new components in" 

699 " final record type") 

700 else: 

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

702 

703 self.match("C_KET") 

704 record.set_ast_link(self.ct) 

705 

706 # Finally mark record final if applicable 

707 if is_final: 

708 record.is_final = True 

709 

710 return record 

711 

712 def parse_expression(self, scope): 

713 # lobster-trace: LRM.Expression 

714 assert isinstance(scope, ast.Scope) 

715 

716 n_lhs = self.parse_relation(scope) 

717 

718 if self.peek_kw("and"): 

719 while self.peek_kw("and"): 

720 self.match_kw("and") 

721 t_op = self.ct 

722 a_op = ast.Binary_Operator.LOGICAL_AND 

723 t_op.ast_link = a_op 

724 n_rhs = self.parse_relation(scope) 

725 n_lhs = ast.Binary_Expression( 

726 mh = self.mh, 

727 location = t_op.location, 

728 typ = self.builtin_bool, 

729 operator = a_op, 

730 n_lhs = n_lhs, 

731 n_rhs = n_rhs) 

732 

733 elif self.peek_kw("or"): 

734 while self.peek_kw("or"): 

735 self.match_kw("or") 

736 t_op = self.ct 

737 a_op = ast.Binary_Operator.LOGICAL_OR 

738 t_op.ast_link = a_op 

739 n_rhs = self.parse_relation(scope) 

740 n_lhs = ast.Binary_Expression( 

741 mh = self.mh, 

742 location = t_op.location, 

743 typ = self.builtin_bool, 

744 operator = a_op, 

745 n_lhs = n_lhs, 

746 n_rhs = n_rhs) 

747 

748 elif self.peek_kw("xor"): 

749 self.match_kw("xor") 

750 t_op = self.ct 

751 a_op = ast.Binary_Operator.LOGICAL_XOR 

752 t_op.ast_link = a_op 

753 n_rhs = self.parse_relation(scope) 

754 n_lhs = ast.Binary_Expression( 

755 mh = self.mh, 

756 location = t_op.location, 

757 typ = self.builtin_bool, 

758 operator = a_op, 

759 n_lhs = n_lhs, 

760 n_rhs = n_rhs) 

761 

762 elif self.peek_kw("implies"): 

763 self.match_kw("implies") 

764 t_op = self.ct 

765 a_op = ast.Binary_Operator.LOGICAL_IMPLIES 

766 t_op.ast_link = a_op 

767 n_rhs = self.parse_relation(scope) 

768 n_lhs = ast.Binary_Expression( 

769 mh = self.mh, 

770 location = t_op.location, 

771 typ = self.builtin_bool, 

772 operator = a_op, 

773 n_lhs = n_lhs, 

774 n_rhs = n_rhs) 

775 

776 return n_lhs 

777 

778 def parse_relation(self, scope): 

779 # lobster-trace: LRM.Relation 

780 # lobster-trace: LRM.Operators 

781 assert isinstance(scope, ast.Scope) 

782 relop_mapping = {"==" : ast.Binary_Operator.COMP_EQ, 

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

784 "<" : ast.Binary_Operator.COMP_LT, 

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

786 ">" : ast.Binary_Operator.COMP_GT, 

787 ">=" : ast.Binary_Operator.COMP_GEQ} 

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

789 

790 n_lhs = self.parse_simple_expression(scope) 

791 

792 if self.peek("OPERATOR") and \ 

793 self.nt.value in Parser.COMPARISON_OPERATOR: 

794 self.match("OPERATOR") 

795 t_op = self.ct 

796 a_op = relop_mapping[t_op.value] 

797 t_op.ast_link = a_op 

798 n_rhs = self.parse_simple_expression(scope) 

799 return ast.Binary_Expression( 

800 mh = self.mh, 

801 location = t_op.location, 

802 typ = self.builtin_bool, 

803 operator = a_op, 

804 n_lhs = n_lhs, 

805 n_rhs = n_rhs) 

806 

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

808 if self.peek_kw("not"): 

809 self.match_kw("not") 

810 t_not = self.ct 

811 else: 

812 t_not = None 

813 

814 self.match_kw("in") 

815 t_in = self.ct 

816 

817 n_a = self.parse_simple_expression(scope) 

818 t_n_a = self.ct 

819 if self.peek("RANGE"): 

820 self.match("RANGE") 

821 t_range = self.ct 

822 n_b = self.parse_simple_expression(scope) 

823 n_b.set_ast_link(self.ct) 

824 n_a.set_ast_link(t_n_a) 

825 rv = ast.Range_Test( 

826 mh = self.mh, 

827 location = t_in.location, 

828 typ = self.builtin_bool, 

829 n_lhs = n_lhs, 

830 n_lower = n_a, 

831 n_upper = n_b) 

832 rv.set_ast_link(t_range) 

833 rv.set_ast_link(t_in) 

834 

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

836 rv = ast.Binary_Expression( 

837 mh = self.mh, 

838 location = t_in.location, 

839 typ = self.builtin_bool, 

840 operator = ast.Binary_Operator.STRING_CONTAINS, 

841 n_lhs = n_lhs, 

842 n_rhs = n_a) 

843 rv.set_ast_link(t_in) 

844 

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

846 a_op = ast.Binary_Operator.ARRAY_CONTAINS 

847 t_in.ast_link = a_op 

848 rv = ast.Binary_Expression( 

849 mh = self.mh, 

850 location = t_in.location, 

851 typ = self.builtin_bool, 

852 operator = a_op, 

853 n_lhs = n_lhs, 

854 n_rhs = n_a) 

855 

856 else: 

857 self.mh.error( 

858 n_a.location, 

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

860 " not for %s" % n_a.typ.name) 

861 

862 if t_not is not None: 

863 a_unary_op = ast.Unary_Operator.LOGICAL_NOT 

864 t_not.ast_link = a_unary_op 

865 rv = ast.Unary_Expression( 

866 mh = self.mh, 

867 location = t_not.location, 

868 typ = self.builtin_bool, 

869 operator = a_unary_op, 

870 n_operand = rv) 

871 

872 return rv 

873 

874 else: 

875 return n_lhs 

876 

877 def parse_simple_expression(self, scope): 

878 # lobster-trace: LRM.Simple_Expression 

879 # lobster-trace: LRM.Operators 

880 # lobster-trace: LRM.Unary_Minus_Parsing 

881 assert isinstance(scope, ast.Scope) 

882 un_add_map = {"+" : ast.Unary_Operator.PLUS, 

883 "-" : ast.Unary_Operator.MINUS} 

884 bin_add_map = {"+" : ast.Binary_Operator.PLUS, 

885 "-" : ast.Binary_Operator.MINUS} 

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

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

888 

889 if self.peek("OPERATOR") and \ 

890 self.nt.value in Parser.ADDING_OPERATOR: 

891 self.match("OPERATOR") 

892 t_unary = self.ct 

893 a_unary = un_add_map[t_unary.value] 

894 t_unary.ast_link = a_unary 

895 has_explicit_brackets = self.peek("BRA") 

896 else: 

897 t_unary = None 

898 

899 n_lhs = self.parse_term(scope) 

900 if t_unary: 

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

902 if self.lint_mode and \ 

903 isinstance(n_lhs, ast.Binary_Expression) and \ 

904 not has_explicit_brackets: 

905 self.mh.check(t_unary.location, 

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

907 "brackets to clarify intent" % 

908 n_lhs.to_string(), 

909 "unary_minus_precedence") 

910 

911 n_lhs = ast.Unary_Expression( 

912 mh = self.mh, 

913 location = t_unary.location, 

914 typ = n_lhs.typ, 

915 operator = a_unary, 

916 n_operand = n_lhs) 

917 

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

919 rtyp = self.builtin_str 

920 else: 

921 rtyp = n_lhs.typ 

922 

923 while self.peek("OPERATOR") and \ 

924 self.nt.value in Parser.ADDING_OPERATOR: 

925 self.match("OPERATOR") 

926 t_op = self.ct 

927 a_op = bin_add_map[t_op.value] 

928 t_op.ast_link = a_op 

929 n_rhs = self.parse_term(scope) 

930 n_lhs = ast.Binary_Expression( 

931 mh = self.mh, 

932 location = t_op.location, 

933 typ = rtyp, 

934 operator = a_op, 

935 n_lhs = n_lhs, 

936 n_rhs = n_rhs) 

937 

938 return n_lhs 

939 

940 def parse_term(self, scope): 

941 # lobster-trace: LRM.Term 

942 # lobster-trace: LRM.Operators 

943 assert isinstance(scope, ast.Scope) 

944 mul_map = {"*" : ast.Binary_Operator.TIMES, 

945 "/" : ast.Binary_Operator.DIVIDE, 

946 "%" : ast.Binary_Operator.REMAINDER} 

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

948 

949 n_lhs = self.parse_factor(scope) 

950 while self.peek("OPERATOR") and \ 

951 self.nt.value in Parser.MULTIPLYING_OPERATOR: 

952 self.match("OPERATOR") 

953 t_op = self.ct 

954 a_op = mul_map[t_op.value] 

955 t_op.ast_link = a_op 

956 n_rhs = self.parse_factor(scope) 

957 n_lhs = ast.Binary_Expression( 

958 mh = self.mh, 

959 location = t_op.location, 

960 typ = n_lhs.typ, 

961 operator = a_op, 

962 n_lhs = n_lhs, 

963 n_rhs = n_rhs) 

964 

965 return n_lhs 

966 

967 def parse_factor(self, scope): 

968 # lobster-trace: LRM.Factor 

969 assert isinstance(scope, ast.Scope) 

970 

971 if self.peek_kw("not"): 

972 self.match_kw("not") 

973 t_op = self.ct 

974 n_operand = self.parse_primary(scope) 

975 a_not = ast.Unary_Operator.LOGICAL_NOT 

976 t_op.ast_link = a_not 

977 return ast.Unary_Expression( 

978 mh = self.mh, 

979 location = t_op.location, 

980 typ = self.builtin_bool, 

981 operator = a_not, 

982 n_operand = n_operand) 

983 

984 elif self.peek_kw("abs"): 

985 self.match_kw("abs") 

986 t_op = self.ct 

987 n_operand = self.parse_primary(scope) 

988 a_abs = ast.Unary_Operator.ABSOLUTE_VALUE 

989 t_op.ast_link = a_abs 

990 return ast.Unary_Expression( 

991 mh = self.mh, 

992 location = t_op.location, 

993 typ = n_operand.typ, 

994 operator = a_abs, 

995 n_operand = n_operand) 

996 

997 else: 

998 n_lhs = self.parse_primary(scope) 

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

1000 self.match("OPERATOR") 

1001 t_op = self.ct 

1002 n_rhs = self.parse_primary(scope) 

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

1004 a_binary = ast.Binary_Operator.POWER 

1005 t_op.ast_link = a_binary 

1006 n_lhs = ast.Binary_Expression( 

1007 mh = self.mh, 

1008 location = t_op.location, 

1009 typ = n_lhs.typ, 

1010 operator = a_binary, 

1011 n_lhs = n_lhs, 

1012 n_rhs = n_rhs) 

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

1014 self.mh.error(n_rhs.location, 

1015 "exponent must not be negative") 

1016 return n_lhs 

1017 

1018 def parse_primary(self, scope): 

1019 # lobster-trace: LRM.Primary 

1020 assert isinstance(scope, ast.Scope) 

1021 

1022 if self.peek("INTEGER"): 

1023 # lobster-trace: LRM.Integer_Values 

1024 self.match("INTEGER") 

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

1026 int_lit.set_ast_link(self.ct) 

1027 return int_lit 

1028 

1029 elif self.peek("DECIMAL"): 

1030 # lobster-trace: LRM.Decimal_Values 

1031 self.match("DECIMAL") 

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

1033 dec_lit.set_ast_link(self.ct) 

1034 return dec_lit 

1035 

1036 elif self.peek("STRING"): 

1037 # lobster-trace: LRM.String_Values 

1038 self.match("STRING") 

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

1040 string_lit.set_ast_link(self.ct) 

1041 return string_lit 

1042 

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

1044 # lobster-trace: LRM.Boolean_Values 

1045 self.match("KEYWORD") 

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

1047 bool_lit.set_ast_link(self.ct) 

1048 return bool_lit 

1049 

1050 elif self.peek_kw("null"): 

1051 self.match_kw("null") 

1052 null_lit = ast.Null_Literal(self.ct) 

1053 null_lit.set_ast_link(self.ct) 

1054 return null_lit 

1055 

1056 elif self.peek("BRA"): 

1057 self.match("BRA") 

1058 t_bra = self.ct 

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

1060 rv = self.parse_quantified_expression(scope) 

1061 elif self.peek_kw("if"): 

1062 rv = self.parse_conditional_expression(scope) 

1063 else: 

1064 rv = self.parse_expression(scope) 

1065 rv.set_ast_link(t_bra) 

1066 self.match("KET") 

1067 rv.set_ast_link(self.ct) 

1068 return rv 

1069 

1070 else: 

1071 return self.parse_name(scope) 

1072 

1073 def parse_quantified_expression(self, scope): 

1074 # lobster-trace: LRM.Quantified_Expression 

1075 assert isinstance(scope, ast.Scope) 

1076 

1077 if self.peek_kw("forall"): 

1078 self.match_kw("forall") 

1079 t_quantified = self.ct 

1080 universal = True 

1081 else: 

1082 self.match_kw("exists") 

1083 t_quantified = self.ct 

1084 universal = False 

1085 loc = self.ct.location 

1086 self.match("IDENTIFIER") 

1087 t_qv = self.ct 

1088 if scope.contains(t_qv.value): 

1089 # lobster-trace: LRM.Quantification_Naming_Scope 

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

1091 self.mh.error(t_qv.location, 

1092 "shadows %s %s from %s" % 

1093 (pdef.__class__.__name__, 

1094 pdef.name, 

1095 self.mh.cross_file_reference(pdef.location))) 

1096 self.match_kw("in") 

1097 t_in = self.ct 

1098 self.match("IDENTIFIER") 

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

1100 n_source = ast.Name_Reference(self.ct.location, 

1101 field) 

1102 n_source.set_ast_link(self.ct) 

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

1104 # lobster-trace: LRM.Quantification_Object 

1105 self.mh.error(self.ct.location, 

1106 "you can only quantify over arrays") 

1107 n_var = ast.Quantified_Variable(t_qv.value, 

1108 t_qv.location, 

1109 field.n_typ.element_type) 

1110 n_var.set_ast_link(t_qv) 

1111 self.match("ARROW") 

1112 t_arrow = self.ct 

1113 

1114 new_table = ast.Symbol_Table() 

1115 new_table.register(self.mh, n_var) 

1116 scope.push(new_table) 

1117 n_expr = self.parse_expression(scope) 

1118 scope.pop() 

1119 

1120 quantified_expression = ast.Quantified_Expression( 

1121 mh = self.mh, 

1122 location = loc, 

1123 typ = self.builtin_bool, 

1124 universal = universal, 

1125 n_variable = n_var, 

1126 n_source = n_source, 

1127 n_expr = n_expr) 

1128 

1129 quantified_expression.set_ast_link(t_quantified) 

1130 quantified_expression.set_ast_link(t_in) 

1131 quantified_expression.set_ast_link(t_arrow) 

1132 

1133 return quantified_expression 

1134 

1135 def parse_conditional_expression(self, scope): 

1136 # lobster-trace: LRM.Conditional_Expression 

1137 # lobster-trace: LRM.Restricted_Null 

1138 assert isinstance(scope, ast.Scope) 

1139 

1140 self.match_kw("if") 

1141 t_if = self.ct 

1142 if_cond = self.parse_expression(scope) 

1143 self.match_kw("then") 

1144 t_then = self.ct 

1145 if_expr = self.parse_expression(scope) 

1146 if if_expr.typ is None: 

1147 self.mh.error(if_expr.location, 

1148 "null is not permitted here") 

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

1150 

1151 rv = ast.Conditional_Expression(location = t_if.location, 

1152 if_action = if_action) 

1153 if_action.set_ast_link(t_if) 

1154 if_action.set_ast_link(t_then) 

1155 

1156 while self.peek_kw("elsif"): 

1157 self.match_kw("elsif") 

1158 t_elsif = self.ct 

1159 elsif_cond = self.parse_expression(scope) 

1160 self.match_kw("then") 

1161 t_then = self.ct 

1162 elsif_expr = self.parse_expression(scope) 

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

1164 elsif_action.set_ast_link(t_elsif) 

1165 elsif_action.set_ast_link(t_then) 

1166 rv.add_elsif(self.mh, elsif_action) 

1167 

1168 self.match_kw("else") 

1169 rv.set_ast_link(self.ct) 

1170 else_expr = self.parse_expression(scope) 

1171 rv.set_else_part(self.mh, else_expr) 

1172 

1173 return rv 

1174 

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

1176 # lobster-trace: LRM.Builtin_Functions 

1177 # lobster-trace: LRM.Builtin_Type_Conversion_Functions 

1178 assert isinstance(scope, ast.Scope) 

1179 assert isinstance(n_name, (ast.Builtin_Function, 

1180 ast.Builtin_Numeric_Type)) 

1181 assert isinstance(t_name, Token) 

1182 

1183 # Parse the arguments. 

1184 parameters = [] 

1185 n_name.set_ast_link(self.ct) 

1186 self.match("BRA") 

1187 n_name.set_ast_link(self.ct) 

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

1189 exp = self.parse_expression(scope) 

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

1191 exp.set_ast_link(self.ct) 

1192 parameters.append(exp) 

1193 

1194 if self.peek("COMMA"): 1194 ↛ 1198line 1194 didn't jump to line 1198 because the condition on line 1194 was always true

1195 self.match("COMMA") 

1196 n_name.set_ast_link(self.ct) 

1197 else: 

1198 break 

1199 self.match("KET") 

1200 n_name.set_ast_link(self.ct) 

1201 

1202 # Enforce arity 

1203 if isinstance(n_name, ast.Builtin_Function): 

1204 required_arity = n_name.arity 

1205 precise = not n_name.arity_at_least 

1206 else: 

1207 required_arity = 1 

1208 precise = True 

1209 

1210 if precise: 

1211 if required_arity != len(parameters): 

1212 self.mh.error(t_name.location, 

1213 "function requires %u parameters" % 

1214 n_name.arity) 

1215 else: 

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

1217 self.mh.error(t_name.location, 

1218 "function requires at least %u parameters" % 

1219 n_name.arity) 

1220 

1221 # Enforce types 

1222 if n_name.name == "len": 

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

1224 return ast.Unary_Expression( 

1225 mh = self.mh, 

1226 location = t_name.location, 

1227 typ = self.builtin_int, 

1228 operator = ast.Unary_Operator.STRING_LENGTH, 

1229 n_operand = parameters[0]) 

1230 else: 

1231 return ast.Unary_Expression( 

1232 mh = self.mh, 

1233 location = t_name.location, 

1234 typ = self.builtin_int, 

1235 operator = ast.Unary_Operator.ARRAY_LENGTH, 

1236 n_operand = parameters[0]) 

1237 

1238 elif n_name.name in ("startswith", 

1239 "endswith"): 

1240 return ast.Binary_Expression( 

1241 mh = self.mh, 

1242 location = t_name.location, 

1243 typ = self.builtin_bool, 

1244 operator = (ast.Binary_Operator.STRING_STARTSWITH 

1245 if "startswith" in n_name.name 

1246 else ast.Binary_Operator.STRING_ENDSWITH), 

1247 n_lhs = parameters[0], 

1248 n_rhs = parameters[1]) 

1249 

1250 elif n_name.name == "matches": 

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

1252 try: 

1253 # lobster-trace: LRM.Static_Regular_Expression 

1254 # scope is None on purpose to enforce static context 

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

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

1257 re.compile(value.value) 

1258 except re.error as err: 

1259 self.mh.error(value.location, 

1260 str(err)) 

1261 return ast.Binary_Expression( 

1262 mh = self.mh, 

1263 location = t_name.location, 

1264 typ = self.builtin_bool, 

1265 operator = ast.Binary_Operator.STRING_REGEX, 

1266 n_lhs = parameters[0], 

1267 n_rhs = parameters[1]) 

1268 

1269 elif n_name.name == "oneof": 

1270 return ast.OneOf_Expression( 

1271 mh = self.mh, 

1272 location = t_name.location, 

1273 typ = self.builtin_bool, 

1274 choices = parameters) 

1275 

1276 elif isinstance(n_name, ast.Builtin_Numeric_Type): 

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

1278 if isinstance(n_name, ast.Builtin_Integer): 

1279 return ast.Unary_Expression( 

1280 mh = self.mh, 

1281 location = t_name.location, 

1282 typ = self.builtin_int, 

1283 operator = ast.Unary_Operator.CONVERSION_TO_INT, 

1284 n_operand = parameters[0]) 

1285 elif isinstance(n_name, ast.Builtin_Decimal): 

1286 return ast.Unary_Expression( 

1287 mh = self.mh, 

1288 location = t_name.location, 

1289 typ = self.builtin_decimal, 

1290 operator = ast.Unary_Operator.CONVERSION_TO_DECIMAL, 

1291 n_operand = parameters[0]) 

1292 else: 

1293 self.mh.ice_loc(t_name.location, 

1294 "unexpected type conversion") 

1295 

1296 else: 

1297 self.mh.ice_loc(t_name.location, 

1298 "unexpected builtin") 

1299 

1300 def parse_name(self, scope): 

1301 # lobster-trace: LRM.Names 

1302 

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

1304 # 

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

1306 # 

1307 # name ::= qualified_name 

1308 # | name '.' IDENTIFIER 

1309 # | name '[' expression ']' 

1310 # | name '(' parameter_list ')' 

1311 # 

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

1313 

1314 assert isinstance(scope, ast.Scope) 

1315 

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

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

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

1319 # components. However as functions cannot be stored in 

1320 # components the true grammar for function calls is always 

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

1322 

1323 # lobster-trace: LRM.Builtin_Functions 

1324 # lobster-trace: LRM.Builtin_Type_Conversion_Functions 

1325 self.match("IDENTIFIER") 

1326 if self.peek("BRA"): 

1327 # If we follow our name with brackets 

1328 # immediately, we have a builtin function call. 

1329 n_name = self.stab.lookup(self.mh, 

1330 self.ct) 

1331 if not isinstance(n_name, (ast.Builtin_Function, 1331 ↛ 1333line 1331 didn't jump to line 1333 because the condition on line 1331 was never true

1332 ast.Builtin_Numeric_Type)): 

1333 self.mh.error(self.ct.location, 

1334 "not a valid builtin function " 

1335 "or numeric type") 

1336 else: 

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

1338 

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

1340 # first. 

1341 if isinstance(n_name, ast.Enumeration_Type): 

1342 n_name.set_ast_link(self.ct) 

1343 self.match("DOT") 

1344 n_name.set_ast_link(self.ct) 

1345 self.match("IDENTIFIER") 

1346 lit = n_name.literals.lookup(self.mh, 

1347 self.ct, 

1348 ast.Enumeration_Literal_Spec) 

1349 enum_lit = ast.Enumeration_Literal(location = self.ct.location, 

1350 literal = lit) 

1351 enum_lit.set_ast_link(self.ct) 

1352 return enum_lit 

1353 

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

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

1356 if not isinstance(n_name, (ast.Builtin_Function, 1356 ↛ 1361line 1356 didn't jump to line 1361 because the condition on line 1356 was never true

1357 ast.Builtin_Numeric_Type, 

1358 ast.Composite_Component, 

1359 ast.Quantified_Variable, 

1360 )): 

1361 self.mh.error(self.ct.location, 

1362 "%s %s is not a valid name" % 

1363 (n_name.__class__.__name__, 

1364 n_name.name)) 

1365 

1366 # Right now function calls and type conversions must be 

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

1368 if isinstance(n_name, (ast.Builtin_Function, 

1369 ast.Builtin_Numeric_Type)): 

1370 # lobster-trace: LRM.Builtin_Functions 

1371 # lobster-trace: LRM.Builtin_Type_Conversion_Functions 

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

1373 

1374 assert isinstance(n_name, (ast.Composite_Component, 

1375 ast.Quantified_Variable)) 

1376 

1377 # We now process the potentially recursive part: 

1378 # | name '.' IDENTIFIER 

1379 # | name '[' expression ']' 

1380 n_name = ast.Name_Reference(location = self.ct.location, 

1381 entity = n_name) 

1382 n_name.set_ast_link(self.ct) 

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

1384 if self.peek("DOT"): 

1385 if not isinstance(n_name.typ, ast.Tuple_Type): 

1386 # lobster-trace: LRM.Valid_Index_Prefixes 

1387 self.mh.error(n_name.location, 

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

1389 "which is not a tuple" % 

1390 (n_name.to_string(), 

1391 n_name.typ.name)) 

1392 self.match("DOT") 

1393 t_dot = self.ct 

1394 self.match("IDENTIFIER") 

1395 n_field = n_name.typ.components.lookup(self.mh, 

1396 self.ct, 

1397 ast.Composite_Component) 

1398 n_field.set_ast_link(self.ct) 

1399 n_name = ast.Field_Access_Expression( 

1400 mh = self.mh, 

1401 location = self.ct.location, 

1402 n_prefix = n_name, 

1403 n_field = n_field) 

1404 n_name.set_ast_link(t_dot) 

1405 

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

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

1408 self.mh.error(n_name.location, 

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

1410 "which is not an array" % 

1411 (n_name.to_string(), 

1412 n_name.typ.name)) 

1413 

1414 self.match("S_BRA") 

1415 t_bracket = self.ct 

1416 n_index = self.parse_expression(scope) 

1417 self.match("S_KET") 

1418 a_binary = ast.Binary_Operator.INDEX 

1419 t_bracket.ast_link = a_binary 

1420 self.ct.ast_link = a_binary 

1421 

1422 n_name = ast.Binary_Expression( 

1423 mh = self.mh, 

1424 location = t_bracket.location, 

1425 typ = n_name.typ.element_type, 

1426 operator = a_binary, 

1427 n_lhs = n_name, 

1428 n_rhs = n_index) 

1429 

1430 return n_name 

1431 

1432 def parse_check_block(self): 

1433 # lobster-trace: LRM.Check_Block 

1434 t_severity = None 

1435 self.match_kw("checks") 

1436 t_checks = self.ct 

1437 self.match("IDENTIFIER") 

1438 # lobster-trace: LRM.Applicable_Types 

1439 # lobster-trace: LRM.Applicable_Components 

1440 n_ctype = self.cu.package.symbols.lookup(self.mh, 

1441 self.ct, 

1442 ast.Composite_Type) 

1443 n_check_block = ast.Check_Block(location = self.ct.location, 

1444 n_typ = n_ctype) 

1445 n_check_block.set_ast_link(t_checks) 

1446 n_ctype.set_ast_link(self.ct) 

1447 scope = ast.Scope() 

1448 scope.push(self.stab) 

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

1450 scope.push(n_ctype.components) 

1451 self.match("C_BRA") 

1452 n_check_block.set_ast_link(self.ct) 

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

1454 c_expr = self.parse_expression(scope) 

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

1456 self.mh.error(c_expr.location, 

1457 "check expression must be Boolean") 

1458 

1459 self.match("COMMA") 

1460 t_first_comma = self.ct 

1461 if self.peek("KEYWORD"): 

1462 self.match("KEYWORD") 

1463 t_severity = self.ct 

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

1465 self.mh.error(self.ct.location, 

1466 "expected warning|error|fatal") 

1467 c_sev = self.ct.value 

1468 else: 

1469 c_sev = "error" 

1470 

1471 self.match("STRING") 

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

1473 # lobster-trace: LRM.No_Newlines_In_Message 

1474 self.mh.error(self.ct.location, 

1475 "error message must not contain a newline", 

1476 fatal = False) 

1477 t_msg = self.ct 

1478 

1479 has_extrainfo = False 

1480 has_anchor = False 

1481 if self.peek("COMMA"): 

1482 self.match("COMMA") 

1483 t_second_comma = self.ct 

1484 if self.peek("IDENTIFIER"): 

1485 has_anchor = True 

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

1487 has_extrainfo = True 

1488 else: 

1489 self.mh.error(self.nt.location, 

1490 "expected either a details string or" 

1491 " identifier to anchor the check message") 

1492 

1493 if has_extrainfo: 

1494 self.match("STRING") 

1495 t_extrainfo = self.ct 

1496 c_extrainfo = self.ct.value 

1497 

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

1499 self.match("COMMA") 

1500 t_third_comma = self.ct 

1501 has_anchor = True 

1502 

1503 else: 

1504 c_extrainfo = None 

1505 

1506 if has_anchor: 

1507 self.match("IDENTIFIER") 

1508 t_anchor = self.ct 

1509 c_anchor = n_ctype.components.lookup(self.mh, 

1510 self.ct, 

1511 ast.Composite_Component) 

1512 else: 

1513 c_anchor = None 

1514 

1515 n_check = ast.Check(n_type = n_ctype, 

1516 n_expr = c_expr, 

1517 n_anchor = c_anchor, 

1518 severity = c_sev, 

1519 t_message = t_msg, 

1520 extrainfo = c_extrainfo) 

1521 

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

1523 # pylint: disable=used-before-assignment 

1524 

1525 n_check.set_ast_link(t_first_comma) 

1526 if t_severity: 

1527 n_check.set_ast_link(t_severity) 

1528 n_check.set_ast_link(t_msg) 

1529 if c_extrainfo or c_anchor: 

1530 n_check.set_ast_link(t_second_comma) 

1531 if c_extrainfo: 

1532 n_check.set_ast_link(t_extrainfo) 

1533 if c_anchor: 

1534 c_anchor.set_ast_link(t_anchor) 

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

1536 n_check.set_ast_link(t_third_comma) 

1537 

1538 n_ctype.add_check(n_check) 

1539 n_check_block.add_check(n_check) 

1540 

1541 assert scope.size() == 3 

1542 

1543 self.match("C_KET") 

1544 n_check_block.set_ast_link(self.ct) 

1545 

1546 return n_check_block 

1547 

1548 def parse_section_declaration(self): 

1549 # lobster-trace: LRM.Section_Declaration 

1550 self.match_kw("section") 

1551 t_section = self.ct 

1552 self.match("STRING") 

1553 sec = ast.Section(name = self.ct.value, 

1554 location = self.ct.location, 

1555 parent = self.section[-1] if self.section else None) 

1556 sec.set_ast_link(self.ct) 

1557 sec.set_ast_link(t_section) 

1558 self.section.append(sec) 

1559 self.match("C_BRA") 

1560 sec.set_ast_link(self.ct) 

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

1562 self.parse_trlc_entry() 

1563 self.match("C_KET") 

1564 sec.set_ast_link(self.ct) 

1565 self.section.pop() 

1566 

1567 def parse_boolean(self): 

1568 # lobster-trace: LRM.Boolean_Values 

1569 self.match("KEYWORD") 

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

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

1572 else: 

1573 self.mh.error(self.ct.location, 

1574 "expected boolean literal (true or false)") 

1575 

1576 def parse_value(self, typ): 

1577 # lobster-trace: LRM.Tuple_Syntax_Correct_Form 

1578 assert isinstance(typ, ast.Type) 

1579 

1580 if isinstance(typ, ast.Builtin_Numeric_Type): 

1581 # lobster-trace: LRM.Integer_Values 

1582 # lobster-trace: LRM.Decimal_Values 

1583 if self.peek("OPERATOR") and \ 

1584 self.nt.value in Parser.ADDING_OPERATOR: 

1585 self.match("OPERATOR") 

1586 t_op = self.ct 

1587 e_op = (ast.Unary_Operator.PLUS 

1588 if t_op.value == "+" 

1589 else ast.Unary_Operator.MINUS) 

1590 t_op.ast_link = e_op 

1591 else: 

1592 t_op = None 

1593 

1594 if isinstance(typ, ast.Builtin_Decimal): 

1595 self.match("DECIMAL") 

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

1597 rv.set_ast_link(self.ct) 

1598 elif isinstance(typ, ast.Builtin_Integer): 

1599 self.match("INTEGER") 

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

1601 rv.set_ast_link(self.ct) 

1602 else: 

1603 assert False 

1604 

1605 if t_op: 

1606 rv = ast.Unary_Expression(mh = self.mh, 

1607 location = t_op.location, 

1608 typ = rv.typ, 

1609 operator = e_op, 

1610 n_operand = rv) 

1611 

1612 return rv 

1613 

1614 elif isinstance(typ, ast.Builtin_Markup_String): 

1615 # lobster-trace: LRM.Markup_String_Values 

1616 return self.parse_markup_string() 

1617 

1618 elif isinstance(typ, ast.Builtin_String): 

1619 # lobster-trace: LRM.String_Values 

1620 self.match("STRING") 

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

1622 rv.set_ast_link(self.ct) 

1623 return rv 

1624 

1625 elif isinstance(typ, ast.Builtin_Boolean): 

1626 rv = self.parse_boolean() 

1627 rv.set_ast_link(self.ct) 

1628 return rv 

1629 

1630 elif isinstance(typ, ast.Array_Type): 

1631 self.match("S_BRA") 

1632 rv = ast.Array_Aggregate(self.ct.location, 

1633 typ) 

1634 rv.set_ast_link(self.ct) 

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

1636 array_elem = self.parse_value(typ.element_type) 

1637 rv.append(array_elem) 

1638 if self.peek("COMMA"): 

1639 self.match("COMMA") 

1640 rv.set_ast_link(self.ct) 

1641 elif self.peek("S_KET") or self.nt is None: 1641 ↛ anywhereline 1641 didn't jump anywhere: it always raised an exception.

1642 break 

1643 else: 

1644 self.mh.error(self.ct.location, 

1645 "comma separating array elements is " 

1646 "missing", 

1647 fatal = False) 

1648 

1649 self.match("S_KET") 

1650 rv.set_ast_link(self.ct) 

1651 

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

1653 self.mh.error(self.ct.location, 

1654 "this array requires at least %u elements " 

1655 "(only %u provided)" % 

1656 (typ.lower_bound, 

1657 len(rv.value)), 

1658 fatal=False) 

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

1660 self.mh.error(rv.value[typ.upper_bound].location, 

1661 "this array requires at most %u elements " 

1662 "(%u provided)" % 

1663 (typ.upper_bound, 

1664 len(rv.value)), 

1665 fatal=False) 

1666 

1667 return rv 

1668 

1669 elif isinstance(typ, ast.Enumeration_Type): 

1670 enum = self.parse_qualified_name(self.default_scope, 

1671 ast.Enumeration_Type) 

1672 enum.set_ast_link(self.ct) 

1673 if enum != typ: 

1674 self.mh.error(self.ct.location, 

1675 "expected %s" % typ.name) 

1676 self.match("DOT") 

1677 enum.set_ast_link(self.ct) 

1678 self.match("IDENTIFIER") 

1679 lit = enum.literals.lookup(self.mh, 

1680 self.ct, 

1681 ast.Enumeration_Literal_Spec) 

1682 return ast.Enumeration_Literal(self.ct.location, 

1683 lit) 

1684 

1685 elif isinstance(typ, ast.Record_Type): 

1686 self.match("IDENTIFIER") 

1687 t_name = self.ct 

1688 if self.peek("DOT"): 

1689 self.match("DOT") 

1690 t_dot = self.ct 

1691 self.match("IDENTIFIER") 

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

1693 the_pkg.set_ast_link(t_name) 

1694 the_pkg.set_ast_link(t_dot) 

1695 if not self.cu.is_visible(the_pkg): 1695 ↛ 1696line 1695 didn't jump to line 1696 because the condition on line 1695 was never true

1696 self.mh.error(self.ct.location, 

1697 "package must be imported before use") 

1698 t_name = self.ct 

1699 else: 

1700 the_pkg = self.cu.package 

1701 

1702 rv = ast.Record_Reference(location = t_name.location, 

1703 name = t_name.value, 

1704 typ = typ, 

1705 package = the_pkg) 

1706 rv.set_ast_link(t_name) 

1707 

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

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

1710 rv.resolve_references(self.mh) 

1711 

1712 return rv 

1713 

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

1715 # lobster-trace: LRM.Tuple_Separator_Form 

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

1717 

1718 next_is_optional = False 

1719 for n_item in typ.iter_sequence(): 

1720 if isinstance(n_item, ast.Composite_Component): 

1721 if next_is_optional and n_item.optional: 

1722 break 

1723 value = self.parse_value(n_item.n_typ) 

1724 rv.assign(n_item.name, value) 

1725 

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

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

1728 self.match(n_item.token.kind) 

1729 n_item.set_ast_link(self.ct) 

1730 else: 

1731 next_is_optional = True 

1732 

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

1734 if self.peek("IDENTIFIER") and \ 

1735 self.nt.value == n_item.token.value: 

1736 self.match("IDENTIFIER") 

1737 n_item.set_ast_link(self.ct) 

1738 else: 

1739 next_is_optional = True 

1740 

1741 else: 

1742 assert False 

1743 

1744 return rv 

1745 

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

1747 # lobster-trace: LRM.Tuple_Generic_Form 

1748 self.match("BRA") 

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

1750 rv.set_ast_link(self.ct) 

1751 

1752 first = True 

1753 for n_field in typ.iter_sequence(): 

1754 if first: 

1755 first = False 

1756 else: 

1757 self.match("COMMA") 

1758 rv.set_ast_link(self.ct) 

1759 rv.assign(n_field.name, 

1760 self.parse_value(n_field.n_typ)) 

1761 

1762 self.match("KET") 

1763 rv.set_ast_link(self.ct) 

1764 return rv 

1765 

1766 else: 

1767 self.mh.ice_loc(self.ct.location, 

1768 "logic error: unexpected type %s" % 

1769 typ.__class__.__name__) 

1770 

1771 def parse_markup_string(self): 

1772 # lobster-trace: LRM.Markup_String_Values 

1773 self.match("STRING") 

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

1775 mpar = Markup_Parser(self, rv) 

1776 mpar.parse_all_references() 

1777 return rv 

1778 

1779 def parse_record_object_declaration(self): 

1780 # lobster-trace: LRM.Section_Declaration 

1781 # lobster-trace: LRM.Record_Object_Declaration 

1782 # lobster-trace: LRM.Valid_Record_Types 

1783 # lobster-trace: LRM.Valid_Components 

1784 # lobster-trace: LRM.Valid_Enumeration_Literals 

1785 # lobster-trace: LRM.Mandatory_Components 

1786 # lobster-trace: LRM.Evaluation_Of_Checks 

1787 # lobster-trace: LRM.Single_Value_Assignment 

1788 

1789 r_typ = self.parse_qualified_name(self.default_scope, 

1790 ast.Record_Type) 

1791 r_typ.set_ast_link(self.ct) 

1792 if r_typ.is_abstract: 

1793 self.mh.error(self.ct.location, 

1794 "cannot declare object of abstract record type %s" % 

1795 r_typ.name) 

1796 

1797 self.match("IDENTIFIER") 

1798 obj = ast.Record_Object( 

1799 name = self.ct.value, 

1800 location = self.ct.location, 

1801 n_typ = r_typ, 

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

1803 n_package = self.cu.package) 

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

1805 obj.set_ast_link(self.ct) 

1806 

1807 self.match("C_BRA") 

1808 obj.set_ast_link(self.ct) 

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

1810 self.match("IDENTIFIER") 

1811 comp = r_typ.components.lookup(self.mh, 

1812 self.ct, 

1813 ast.Composite_Component) 

1814 if obj.is_component_implicit_null(comp): 

1815 self.mh.error(self.ct.location, 

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

1817 (comp.name, 

1818 obj.field[comp.name].location.line_no)) 

1819 comp.set_ast_link(self.ct) 

1820 if r_typ.is_frozen(comp): 

1821 self.mh.error(self.ct.location, 

1822 "cannot overwrite frozen component %s" % 

1823 comp.name) 

1824 self.match("ASSIGN") 

1825 comp.set_ast_link(self.ct) 

1826 value = self.parse_value(comp.n_typ) 

1827 if not self.ct.ast_link: 

1828 value.set_ast_link(self.ct) 

1829 obj.assign(comp, value) 

1830 

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

1832 for comp in r_typ.all_components(): 

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

1834 if r_typ.is_frozen(comp): 

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

1836 elif not comp.optional: 

1837 self.mh.error( 

1838 obj.location, 

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

1840 (comp.name, 

1841 self.mh.cross_file_reference(comp.location))) 

1842 

1843 self.match("C_KET") 

1844 obj.set_ast_link(self.ct) 

1845 

1846 return obj 

1847 

1848 def parse_trlc_entry(self): 

1849 # lobster-trace: LRM.TRLC_File 

1850 if self.peek_kw("section"): 

1851 self.parse_section_declaration() 

1852 else: 

1853 self.cu.add_item(self.parse_record_object_declaration()) 

1854 

1855 def parse_preamble(self, kind): 

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

1857 # lobster-trace: LRM.Layout 

1858 # lobster-trace: LRM.Preamble 

1859 

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

1861 # needed 

1862 self.match_kw("package") 

1863 t_pkg = self.ct 

1864 self.match("IDENTIFIER") 

1865 

1866 if kind == "rsl": 

1867 declare_package = True 

1868 else: 

1869 # lobster-trace: LRM.Late_Package_Declarations 

1870 declare_package = not self.stab.contains(self.ct.value) 

1871 

1872 if declare_package: 

1873 # lobster-trace: LRM.Package_Declaration 

1874 pkg = ast.Package(name = self.ct.value, 

1875 location = self.ct.location, 

1876 builtin_stab = self.stab, 

1877 declared_late = kind == "trlc") 

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

1879 else: 

1880 pkg = self.stab.lookup(self.mh, self.ct, ast.Package) 

1881 

1882 pkg.set_ast_link(t_pkg) 

1883 pkg.set_ast_link(self.ct) 

1884 

1885 # lobster-trace: LRM.Current_Package 

1886 self.cu.set_package(pkg) 

1887 

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

1889 

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

1891 # lobster-trace: LRM.Import_Visibility 

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

1893 while self.peek_kw("import"): 

1894 self.match_kw("import") 

1895 pkg.set_ast_link(self.ct) 

1896 self.match("IDENTIFIER") 

1897 self.cu.add_import(self.mh, self.ct) 

1898 

1899 def parse_rsl_file(self): 

1900 # lobster-trace: LRM.RSL_File 

1901 assert self.cu.package is not None 

1902 

1903 ok = True 

1904 while not self.peek_eof(): 

1905 try: 

1906 if self.peek_kw("checks"): 

1907 self.cu.add_item(self.parse_check_block()) 

1908 else: 

1909 self.cu.add_item(self.parse_type_declaration()) 

1910 except TRLC_Error as err: 

1911 if not self.error_recovery or err.kind == "lex error": 1911 ↛ 1912line 1911 didn't jump to line 1912 because the condition on line 1911 was never true

1912 raise 

1913 

1914 ok = False 

1915 

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

1917 # relevant keyword 

1918 self.skip_until_newline() 

1919 while not self.peek_eof(): 

1920 if self.peek_kw("checks") or \ 

1921 self.peek_kw("type") or \ 

1922 self.peek_kw("abstract") or \ 

1923 self.peek_kw("final") or \ 

1924 self.peek_kw("tuple") or \ 

1925 self.peek_kw("enum"): 

1926 break 

1927 self.advance() 

1928 self.skip_until_newline() 

1929 

1930 self.match_eof() 

1931 

1932 for tok in self.lexer.tokens: 

1933 if tok.kind == "COMMENT": 

1934 self.cu.package.set_ast_link(tok) 

1935 

1936 return ok 

1937 

1938 def parse_trlc_file(self): 

1939 # lobster-trace: LRM.TRLC_File 

1940 assert self.cu.package is not None 

1941 

1942 ok = True 

1943 

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

1945 try: 

1946 self.parse_trlc_entry() 

1947 except TRLC_Error as err: 

1948 if not self.error_recovery or err.kind == "lex error": 1948 ↛ 1949line 1948 didn't jump to line 1949 because the condition on line 1948 was never true

1949 raise 

1950 

1951 ok = False 

1952 

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

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

1955 # EOF 

1956 self.skip_until_newline() 

1957 while not self.peek_eof(): 

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

1959 break 

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

1961 pass 

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

1963 n_sym = self.stab.lookup_assuming(self.mh, 

1964 self.nt.value) 

1965 if isinstance(n_sym, ast.Package): 

1966 break 

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

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

1969 self.mh, 

1970 self.nt.value) 

1971 if isinstance(n_sym, ast.Record_Type): 

1972 break 

1973 self.advance() 

1974 self.skip_until_newline() 

1975 

1976 self.match_eof() 

1977 

1978 for tok in self.lexer.tokens: 

1979 if tok.kind == "COMMENT": 

1980 self.cu.package.set_ast_link(tok) 

1981 

1982 return ok