Coverage for trlc/parser.py: 96%

1123 statements  

« prev     ^ index     » next       coverage.py v7.10.7, created at 2026-09-04 11:46 +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 def __init__( 

294 self, 

295 mh, 

296 stab, 

297 file_name, 

298 lint_mode, 

299 error_recovery, 

300 primary_file=True, 

301 lexer=None, 

302 ): 

303 assert isinstance(mh, Message_Handler) 

304 assert isinstance(stab, ast.Symbol_Table) 

305 assert isinstance(file_name, str) 

306 assert isinstance(lint_mode, bool) 

307 assert isinstance(error_recovery, bool) 

308 assert isinstance(primary_file, bool) 

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

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

311 super().__init__( 

312 mh, 

313 lexer, 

314 eoc_name="end-of-file", 

315 token_map=Token.KIND, 

316 keywords=TRLC_Lexer.KEYWORDS, 

317 ) 

318 else: 

319 super().__init__( 

320 mh, 

321 TRLC_Lexer(mh, file_name), 

322 eoc_name="end-of-file", 

323 token_map=Token.KIND, 

324 keywords=TRLC_Lexer.KEYWORDS, 

325 ) 

326 

327 self.lint_mode = lint_mode 

328 self.error_recovery = error_recovery 

329 

330 self.stab = stab 

331 self.cu = ast.Compilation_Unit(file_name) 

332 

333 self.primary = primary_file 

334 self.secondary = False 

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

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

337 # was selected by dependency analysis. 

338 

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

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

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

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

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

344 

345 self.section = [] 

346 self.default_scope = ast.Scope() 

347 self.default_scope.push(self.stab) 

348 

349 def parse_described_name(self): 

350 # lobster-trace: LRM.Described_Names 

351 # lobster-trace: LRM.Described_Name_Description 

352 self.match("IDENTIFIER") 

353 name = self.ct 

354 

355 if self.peek("STRING"): 

356 self.match("STRING") 

357 t_descr = self.ct 

358 return name, t_descr.value, t_descr 

359 else: 

360 return name, None, None 

361 

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

363 # lobster-trace: LRM.Qualified_Name 

364 # lobster-trace: LRM.Valid_Qualifier 

365 # lobster-trace: LRM.Valid_Name 

366 assert isinstance(scope, ast.Scope) 

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

368 assert isinstance(match_ident, bool) 

369 

370 if match_ident: 

371 self.match("IDENTIFIER") 

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

373 sym.set_ast_link(self.ct) 

374 

375 if isinstance(sym, ast.Package): 

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

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

378 self.match("DOT") 

379 sym.set_ast_link(self.ct) 

380 self.match("IDENTIFIER") 

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

382 else: 

383 # Easiest way to generate the correct error message 

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

385 

386 def parse_type_declaration(self): 

387 # lobster-trace: LRM.Type_Declarations 

388 if self.peek_kw("enum"): 

389 n_item = self.parse_enum_declaration() 

390 elif self.peek_kw("tuple"): 

391 n_item = self.parse_tuple_declaration() 

392 else: 

393 n_item = self.parse_record_declaration() 

394 assert isinstance(n_item, ast.Concrete_Type) 

395 return n_item 

396 

397 def parse_enum_declaration(self): 

398 # lobster-trace: LRM.Enumeration_Declaration 

399 self.match_kw("enum") 

400 t_enum = self.ct 

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

402 

403 enum = ast.Enumeration_Type( 

404 name=name.value, 

405 description=description, 

406 location=name.location, 

407 package=self.cu.package, 

408 ) 

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

410 enum.set_ast_link(t_enum) 

411 enum.set_ast_link(name) 

412 if t_description: 

413 enum.set_ast_link(t_description) 

414 

415 self.match("C_BRA") 

416 enum.set_ast_link(self.ct) 

417 empty = True 

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

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

420 lit = ast.Enumeration_Literal_Spec( 

421 name=name.value, 

422 description=description, 

423 location=name.location, 

424 enum=enum, 

425 ) 

426 lit.set_ast_link(name) 

427 if t_description: 

428 lit.set_ast_link(self.ct) 

429 empty = False 

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

431 self.match("C_KET") 

432 enum.set_ast_link(self.ct) 

433 

434 if empty: 

435 # lobster-trace: LRM.No_Empty_Enumerations 

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

437 

438 return enum 

439 

440 def parse_tuple_field( 

441 self, n_tuple, optional_allowed, optional_reason, optional_required 

442 ): 

443 assert isinstance(n_tuple, ast.Tuple_Type) 

444 assert isinstance(optional_allowed, bool) 

445 assert isinstance(optional_reason, str) 

446 assert isinstance(optional_required, bool) 

447 assert optional_allowed or not optional_required 

448 

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

450 

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

452 self.match_kw("optional") 

453 t_optional = self.ct 

454 field_is_optional = True 

455 if not optional_allowed: 

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

457 else: 

458 field_is_optional = False 

459 t_optional = None 

460 

461 # lobster-trace: LRM.Tuple_Field_Types 

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

463 if self.peek("S_BRA"): 

464 # lobster-trace: LRM.union_type 

465 field_type = self.parse_union_type() 

466 else: 

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

468 comp = ast.Composite_Component( 

469 name=field_name.value, 

470 description=field_description, 

471 location=field_name.location, 

472 member_of=n_tuple, 

473 n_typ=field_type, 

474 optional=field_is_optional, 

475 ) 

476 comp.set_ast_link(field_name) 

477 if t_descr: 

478 comp.set_ast_link(t_descr) 

479 if field_is_optional: 

480 comp.set_ast_link(t_optional) 

481 

482 return comp 

483 

484 def parse_tuple_declaration(self): 

485 # lobster-trace: LRM.Tuple_Declaration 

486 self.match_kw("tuple") 

487 t_tuple = self.ct 

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

489 

490 n_tuple = ast.Tuple_Type( 

491 name=name.value, 

492 description=description, 

493 location=name.location, 

494 package=self.cu.package, 

495 ) 

496 

497 n_tuple.set_ast_link(t_tuple) 

498 n_tuple.set_ast_link(name) 

499 if t_descr: 

500 n_tuple.set_ast_link(t_descr) 

501 self.match("C_BRA") 

502 n_tuple.set_ast_link(self.ct) 

503 

504 n_field = self.parse_tuple_field( 

505 n_tuple, 

506 optional_allowed=False, 

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

508 optional_required=False, 

509 ) 

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

511 

512 has_separators = False 

513 optional_required = False 

514 separator_allowed = True 

515 

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

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

518 has_separators = True 

519 self.match_kw("separator") 

520 t_sep = self.ct 

521 if not separator_allowed: 

522 # lobster-trace: LRM.Tuple_Separators_All_Or_None 

523 self.mh.error( 

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

525 ) 

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

527 self.peek("IDENTIFIER") 

528 or self.peek("AT") 

529 or self.peek("COLON") 

530 or self.peek("SEMICOLON") 

531 ): 

532 self.advance() 

533 sep = ast.Separator(self.ct) 

534 sep.set_ast_link(t_sep) 

535 sep.set_ast_link(self.ct) 

536 n_tuple.add_separator(sep) 

537 else: 

538 separator_allowed = False 

539 # lobster-trace: LRM.Tuple_Optional_Requires_Separators 

540 n_field = self.parse_tuple_field( 

541 n_tuple, 

542 optional_allowed=has_separators, 

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

544 optional_required=optional_required, 

545 ) 

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

547 # lobster-trace: LRM.Tuple_Optional_Fields 

548 optional_required |= n_field.optional 

549 

550 self.match("C_KET") 

551 n_tuple.set_ast_link(self.ct) 

552 

553 # Final check to ban tuples with separators containing other 

554 # tuples. 

555 if has_separators: 

556 # lobster-trace: LRM.Restricted_Tuple_Nesting 

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

558 if ( 

559 isinstance(n_field.n_typ, ast.Tuple_Type) 

560 and n_field.n_typ.has_separators() 

561 ): 

562 self.mh.error( 

563 n_field.location, 

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

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

566 ) 

567 

568 # Late registration to avoid recursion in tuples 

569 # lobster-trace: LRM.Tuple_Field_Types 

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

571 

572 return n_tuple 

573 

574 def parse_union_type(self): 

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

576 

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

578 Returns an ast.Union_Type node. 

579 """ 

580 # lobster-trace: LRM.union_type 

581 # lobster-trace: LRM.Union_Type_No_Duplicates 

582 # lobster-trace: LRM.Union_Type_Record_Types_Only 

583 self.match("S_BRA") 

584 t_s_bra = self.ct 

585 

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

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

588 first_type.set_ast_link(self.ct) 

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

590 

591 while self.peek("COMMA"): 

592 self.match("COMMA") 

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

594 next_type.set_ast_link(self.ct) 

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

596 

597 self.match("S_KET") 

598 t_s_ket = self.ct 

599 

600 seen = {} 

601 for t, loc in union_type_entries: 

602 fqn = t.fully_qualified_name() 

603 if fqn in seen: 

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

605 else: 

606 seen[fqn] = loc 

607 

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

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

610 c_typ.set_ast_link(t_s_bra) 

611 c_typ.set_ast_link(t_s_ket) 

612 return c_typ 

613 

614 def parse_record_component(self, n_record): 

615 assert isinstance(n_record, ast.Record_Type) 

616 

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

618 t_optional = None 

619 c_optional = False 

620 if self.peek_kw("optional"): 

621 self.match_kw("optional") 

622 t_optional = self.ct 

623 c_optional = True 

624 

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

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

627 if self.peek("S_BRA"): 

628 c_typ = self.parse_union_type() 

629 else: 

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

631 c_typ.set_ast_link(self.ct) 

632 

633 if self.peek("S_BRA"): 

634 self.match("S_BRA") 

635 t_s_bra = self.ct 

636 self.match("INTEGER") 

637 t_lo = self.ct 

638 a_lo = self.ct.value 

639 loc_lo = self.ct.location 

640 self.match("RANGE") 

641 t_range = self.ct 

642 a_loc = self.ct.location 

643 a_hi = None 

644 if self.peek("INTEGER"): 

645 self.match("INTEGER") 

646 a_hi = self.ct.value 

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

648 self.match("OPERATOR") 

649 else: 

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

651 t_hi = self.ct 

652 loc_hi = self.ct.location 

653 self.match("S_KET") 

654 t_s_ket = self.ct 

655 c_typ = ast.Array_Type( 

656 location=a_loc, 

657 element_type=c_typ, 

658 lower_bound=a_lo, 

659 upper_bound=a_hi, 

660 loc_lower=loc_lo, 

661 loc_upper=loc_hi, 

662 ) 

663 c_typ.set_ast_link(t_s_bra) 

664 c_typ.set_ast_link(t_lo) 

665 c_typ.set_ast_link(t_range) 

666 c_typ.set_ast_link(t_hi) 

667 c_typ.set_ast_link(t_s_ket) 

668 

669 c_comp = ast.Composite_Component( 

670 name=c_name.value, 

671 description=c_descr, 

672 location=c_name.location, 

673 member_of=n_record, 

674 n_typ=c_typ, 

675 optional=c_optional, 

676 ) 

677 c_comp.set_ast_link(c_name) 

678 if t_descr: 

679 c_comp.set_ast_link(t_descr) 

680 if c_optional: 

681 c_comp.set_ast_link(t_optional) 

682 

683 return c_comp 

684 

685 def parse_record_declaration(self): 

686 t_abstract = None 

687 t_final = None 

688 is_abstract = False 

689 is_final = False 

690 if self.peek_kw("abstract"): 

691 self.match_kw("abstract") 

692 t_abstract = self.ct 

693 is_abstract = True 

694 elif self.peek_kw("final"): 

695 self.match_kw("final") 

696 t_final = self.ct 

697 is_final = True 

698 

699 self.match_kw("type") 

700 t_type = self.ct 

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

702 

703 if self.peek_kw("extends"): 

704 self.match_kw("extends") 

705 t_extends = self.ct 

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

707 root_record.set_ast_link(t_extends) 

708 root_record.set_ast_link(self.ct) 

709 else: 

710 root_record = None 

711 

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

713 self.mh.check( 

714 name.location, 

715 "consider clarifying that this record is final", 

716 "clarify_final", 

717 ( 

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

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

720 "clarifies this to casual readers." 

721 % root_record.fully_qualified_name() 

722 ), 

723 ) 

724 

725 record = ast.Record_Type( 

726 name=name.value, 

727 description=description, 

728 location=name.location, 

729 package=self.cu.package, 

730 n_parent=root_record, 

731 is_abstract=is_abstract, 

732 ) 

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

734 if is_abstract: 

735 record.set_ast_link(t_abstract) 

736 if is_final: 

737 record.set_ast_link(t_final) 

738 record.set_ast_link(t_type) 

739 record.set_ast_link(name) 

740 if t_description: 

741 record.set_ast_link(t_description) 

742 

743 self.match("C_BRA") 

744 record.set_ast_link(self.ct) 

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

746 if self.peek_kw("freeze"): 

747 self.match_kw("freeze") 

748 t_freeze = self.ct 

749 self.match("IDENTIFIER") 

750 n_comp = record.components.lookup( 

751 self.mh, self.ct, ast.Composite_Component 

752 ) 

753 if record.is_frozen(n_comp): 

754 n_value = record.get_freezing_expression(n_comp) 

755 self.mh.error( 

756 self.ct.location, 

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

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

759 ) 

760 n_comp.set_ast_link(t_freeze) 

761 n_comp.set_ast_link(self.ct) 

762 self.match("ASSIGN") 

763 n_comp.set_ast_link(self.ct) 

764 n_value = self.parse_value(n_comp.n_typ) 

765 n_value.set_ast_link(self.ct) 

766 

767 record.frozen[n_comp.name] = n_value 

768 

769 else: 

770 n_comp = self.parse_record_component(record) 

771 if record.is_final: 

772 self.mh.error( 

773 n_comp.location, 

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

775 ) 

776 else: 

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

778 

779 self.match("C_KET") 

780 record.set_ast_link(self.ct) 

781 

782 # Finally mark record final if applicable 

783 if is_final: 

784 record.is_final = True 

785 

786 return record 

787 

788 def parse_expression(self, scope): 

789 # lobster-trace: LRM.Expression 

790 assert isinstance(scope, ast.Scope) 

791 

792 n_lhs = self.parse_relation(scope) 

793 

794 if self.peek_kw("and"): 

795 while self.peek_kw("and"): 

796 self.match_kw("and") 

797 t_op = self.ct 

798 a_op = ast.Binary_Operator.LOGICAL_AND 

799 t_op.ast_link = a_op 

800 n_rhs = self.parse_relation(scope) 

801 n_lhs = ast.Binary_Expression( 

802 mh=self.mh, 

803 location=t_op.location, 

804 typ=self.builtin_bool, 

805 operator=a_op, 

806 n_lhs=n_lhs, 

807 n_rhs=n_rhs, 

808 ) 

809 

810 elif self.peek_kw("or"): 

811 while self.peek_kw("or"): 

812 self.match_kw("or") 

813 t_op = self.ct 

814 a_op = ast.Binary_Operator.LOGICAL_OR 

815 t_op.ast_link = a_op 

816 n_rhs = self.parse_relation(scope) 

817 n_lhs = ast.Binary_Expression( 

818 mh=self.mh, 

819 location=t_op.location, 

820 typ=self.builtin_bool, 

821 operator=a_op, 

822 n_lhs=n_lhs, 

823 n_rhs=n_rhs, 

824 ) 

825 

826 elif self.peek_kw("xor"): 

827 self.match_kw("xor") 

828 t_op = self.ct 

829 a_op = ast.Binary_Operator.LOGICAL_XOR 

830 t_op.ast_link = a_op 

831 n_rhs = self.parse_relation(scope) 

832 n_lhs = ast.Binary_Expression( 

833 mh=self.mh, 

834 location=t_op.location, 

835 typ=self.builtin_bool, 

836 operator=a_op, 

837 n_lhs=n_lhs, 

838 n_rhs=n_rhs, 

839 ) 

840 

841 elif self.peek_kw("implies"): 

842 self.match_kw("implies") 

843 t_op = self.ct 

844 a_op = ast.Binary_Operator.LOGICAL_IMPLIES 

845 t_op.ast_link = a_op 

846 n_rhs = self.parse_relation(scope) 

847 n_lhs = ast.Binary_Expression( 

848 mh=self.mh, 

849 location=t_op.location, 

850 typ=self.builtin_bool, 

851 operator=a_op, 

852 n_lhs=n_lhs, 

853 n_rhs=n_rhs, 

854 ) 

855 

856 return n_lhs 

857 

858 def parse_relation(self, scope): 

859 # lobster-trace: LRM.Relation 

860 # lobster-trace: LRM.Operators 

861 assert isinstance(scope, ast.Scope) 

862 relop_mapping = { 

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

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

865 "<": ast.Binary_Operator.COMP_LT, 

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

867 ">": ast.Binary_Operator.COMP_GT, 

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

869 } 

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

871 

872 n_lhs = self.parse_simple_expression(scope) 

873 

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

875 self.match("OPERATOR") 

876 t_op = self.ct 

877 a_op = relop_mapping[t_op.value] 

878 t_op.ast_link = a_op 

879 n_rhs = self.parse_simple_expression(scope) 

880 return ast.Binary_Expression( 

881 mh=self.mh, 

882 location=t_op.location, 

883 typ=self.builtin_bool, 

884 operator=a_op, 

885 n_lhs=n_lhs, 

886 n_rhs=n_rhs, 

887 ) 

888 

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

890 if self.peek_kw("not"): 

891 self.match_kw("not") 

892 t_not = self.ct 

893 else: 

894 t_not = None 

895 

896 self.match_kw("in") 

897 t_in = self.ct 

898 

899 n_a = self.parse_simple_expression(scope) 

900 t_n_a = self.ct 

901 if self.peek("RANGE"): 

902 self.match("RANGE") 

903 t_range = self.ct 

904 n_b = self.parse_simple_expression(scope) 

905 n_b.set_ast_link(self.ct) 

906 n_a.set_ast_link(t_n_a) 

907 rv = ast.Range_Test( 

908 mh=self.mh, 

909 location=t_in.location, 

910 typ=self.builtin_bool, 

911 n_lhs=n_lhs, 

912 n_lower=n_a, 

913 n_upper=n_b, 

914 ) 

915 rv.set_ast_link(t_range) 

916 rv.set_ast_link(t_in) 

917 

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

919 rv = ast.Binary_Expression( 

920 mh=self.mh, 

921 location=t_in.location, 

922 typ=self.builtin_bool, 

923 operator=ast.Binary_Operator.STRING_CONTAINS, 

924 n_lhs=n_lhs, 

925 n_rhs=n_a, 

926 ) 

927 rv.set_ast_link(t_in) 

928 

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

930 a_op = ast.Binary_Operator.ARRAY_CONTAINS 

931 t_in.ast_link = a_op 

932 rv = ast.Binary_Expression( 

933 mh=self.mh, 

934 location=t_in.location, 

935 typ=self.builtin_bool, 

936 operator=a_op, 

937 n_lhs=n_lhs, 

938 n_rhs=n_a, 

939 ) 

940 

941 else: 

942 self.mh.error( 

943 n_a.location, 

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

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

946 ) 

947 

948 if t_not is not None: 

949 a_unary_op = ast.Unary_Operator.LOGICAL_NOT 

950 t_not.ast_link = a_unary_op 

951 rv = ast.Unary_Expression( 

952 mh=self.mh, 

953 location=t_not.location, 

954 typ=self.builtin_bool, 

955 operator=a_unary_op, 

956 n_operand=rv, 

957 ) 

958 

959 return rv 

960 

961 else: 

962 return n_lhs 

963 

964 def parse_simple_expression(self, scope): 

965 # lobster-trace: LRM.Simple_Expression 

966 # lobster-trace: LRM.Operators 

967 # lobster-trace: LRM.Unary_Minus_Parsing 

968 assert isinstance(scope, ast.Scope) 

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

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

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

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

973 

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

975 self.match("OPERATOR") 

976 t_unary = self.ct 

977 a_unary = un_add_map[t_unary.value] 

978 t_unary.ast_link = a_unary 

979 has_explicit_brackets = self.peek("BRA") 

980 else: 

981 t_unary = None 

982 

983 n_lhs = self.parse_term(scope) 

984 if t_unary: 

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

986 if ( 

987 self.lint_mode 

988 and isinstance(n_lhs, ast.Binary_Expression) 

989 and not has_explicit_brackets 

990 ): 

991 self.mh.check( 

992 t_unary.location, 

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

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

995 "unary_minus_precedence", 

996 ) 

997 

998 n_lhs = ast.Unary_Expression( 

999 mh=self.mh, 

1000 location=t_unary.location, 

1001 typ=n_lhs.typ, 

1002 operator=a_unary, 

1003 n_operand=n_lhs, 

1004 ) 

1005 

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

1007 rtyp = self.builtin_str 

1008 else: 

1009 rtyp = n_lhs.typ 

1010 

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

1012 self.match("OPERATOR") 

1013 t_op = self.ct 

1014 a_op = bin_add_map[t_op.value] 

1015 t_op.ast_link = a_op 

1016 n_rhs = self.parse_term(scope) 

1017 n_lhs = ast.Binary_Expression( 

1018 mh=self.mh, 

1019 location=t_op.location, 

1020 typ=rtyp, 

1021 operator=a_op, 

1022 n_lhs=n_lhs, 

1023 n_rhs=n_rhs, 

1024 ) 

1025 

1026 return n_lhs 

1027 

1028 def parse_term(self, scope): 

1029 # lobster-trace: LRM.Term 

1030 # lobster-trace: LRM.Operators 

1031 assert isinstance(scope, ast.Scope) 

1032 mul_map = { 

1033 "*": ast.Binary_Operator.TIMES, 

1034 "/": ast.Binary_Operator.DIVIDE, 

1035 "%": ast.Binary_Operator.REMAINDER, 

1036 } 

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

1038 

1039 n_lhs = self.parse_factor(scope) 

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

1041 self.match("OPERATOR") 

1042 t_op = self.ct 

1043 a_op = mul_map[t_op.value] 

1044 t_op.ast_link = a_op 

1045 n_rhs = self.parse_factor(scope) 

1046 n_lhs = ast.Binary_Expression( 

1047 mh=self.mh, 

1048 location=t_op.location, 

1049 typ=n_lhs.typ, 

1050 operator=a_op, 

1051 n_lhs=n_lhs, 

1052 n_rhs=n_rhs, 

1053 ) 

1054 

1055 return n_lhs 

1056 

1057 def parse_factor(self, scope): 

1058 # lobster-trace: LRM.Factor 

1059 assert isinstance(scope, ast.Scope) 

1060 

1061 if self.peek_kw("not"): 

1062 self.match_kw("not") 

1063 t_op = self.ct 

1064 n_operand = self.parse_primary(scope) 

1065 a_not = ast.Unary_Operator.LOGICAL_NOT 

1066 t_op.ast_link = a_not 

1067 return ast.Unary_Expression( 

1068 mh=self.mh, 

1069 location=t_op.location, 

1070 typ=self.builtin_bool, 

1071 operator=a_not, 

1072 n_operand=n_operand, 

1073 ) 

1074 

1075 elif self.peek_kw("abs"): 

1076 self.match_kw("abs") 

1077 t_op = self.ct 

1078 n_operand = self.parse_primary(scope) 

1079 a_abs = ast.Unary_Operator.ABSOLUTE_VALUE 

1080 t_op.ast_link = a_abs 

1081 return ast.Unary_Expression( 

1082 mh=self.mh, 

1083 location=t_op.location, 

1084 typ=n_operand.typ, 

1085 operator=a_abs, 

1086 n_operand=n_operand, 

1087 ) 

1088 

1089 else: 

1090 n_lhs = self.parse_primary(scope) 

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

1092 self.match("OPERATOR") 

1093 t_op = self.ct 

1094 n_rhs = self.parse_primary(scope) 

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

1096 a_binary = ast.Binary_Operator.POWER 

1097 t_op.ast_link = a_binary 

1098 n_lhs = ast.Binary_Expression( 

1099 mh=self.mh, 

1100 location=t_op.location, 

1101 typ=n_lhs.typ, 

1102 operator=a_binary, 

1103 n_lhs=n_lhs, 

1104 n_rhs=n_rhs, 

1105 ) 

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

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

1108 return n_lhs 

1109 

1110 def parse_primary(self, scope): 

1111 # lobster-trace: LRM.Primary 

1112 assert isinstance(scope, ast.Scope) 

1113 

1114 if self.peek("INTEGER"): 

1115 # lobster-trace: LRM.Integer_Values 

1116 self.match("INTEGER") 

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

1118 int_lit.set_ast_link(self.ct) 

1119 return int_lit 

1120 

1121 elif self.peek("DECIMAL"): 

1122 # lobster-trace: LRM.Decimal_Values 

1123 self.match("DECIMAL") 

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

1125 dec_lit.set_ast_link(self.ct) 

1126 return dec_lit 

1127 

1128 elif self.peek("STRING"): 

1129 # lobster-trace: LRM.String_Values 

1130 self.match("STRING") 

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

1132 string_lit.set_ast_link(self.ct) 

1133 return string_lit 

1134 

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

1136 # lobster-trace: LRM.Boolean_Values 

1137 self.match("KEYWORD") 

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

1139 bool_lit.set_ast_link(self.ct) 

1140 return bool_lit 

1141 

1142 elif self.peek_kw("null"): 

1143 self.match_kw("null") 

1144 null_lit = ast.Null_Literal(self.ct) 

1145 null_lit.set_ast_link(self.ct) 

1146 return null_lit 

1147 

1148 elif self.peek("BRA"): 

1149 self.match("BRA") 

1150 t_bra = self.ct 

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

1152 rv = self.parse_quantified_expression(scope) 

1153 elif self.peek_kw("if"): 

1154 rv = self.parse_conditional_expression(scope) 

1155 else: 

1156 rv = self.parse_expression(scope) 

1157 rv.set_ast_link(t_bra) 

1158 self.match("KET") 

1159 rv.set_ast_link(self.ct) 

1160 return rv 

1161 

1162 else: 

1163 return self.parse_name(scope) 

1164 

1165 def parse_quantified_expression(self, scope): 

1166 # lobster-trace: LRM.Quantified_Expression 

1167 assert isinstance(scope, ast.Scope) 

1168 

1169 if self.peek_kw("forall"): 

1170 self.match_kw("forall") 

1171 t_quantified = self.ct 

1172 universal = True 

1173 else: 

1174 self.match_kw("exists") 

1175 t_quantified = self.ct 

1176 universal = False 

1177 loc = self.ct.location 

1178 self.match("IDENTIFIER") 

1179 t_qv = self.ct 

1180 if scope.contains(t_qv.value): 

1181 # lobster-trace: LRM.Quantification_Naming_Scope 

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

1183 self.mh.error( 

1184 t_qv.location, 

1185 "shadows %s %s from %s" 

1186 % ( 

1187 pdef.__class__.__name__, 

1188 pdef.name, 

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

1190 ), 

1191 ) 

1192 self.match_kw("in") 

1193 t_in = self.ct 

1194 self.match("IDENTIFIER") 

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

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

1197 n_source.set_ast_link(self.ct) 

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

1199 # lobster-trace: LRM.Quantification_Object 

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

1201 n_var = ast.Quantified_Variable( 

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

1203 ) 

1204 n_var.set_ast_link(t_qv) 

1205 self.match("ARROW") 

1206 t_arrow = self.ct 

1207 

1208 new_table = ast.Symbol_Table() 

1209 new_table.register(self.mh, n_var) 

1210 scope.push(new_table) 

1211 n_expr = self.parse_expression(scope) 

1212 scope.pop() 

1213 

1214 quantified_expression = ast.Quantified_Expression( 

1215 mh=self.mh, 

1216 location=loc, 

1217 typ=self.builtin_bool, 

1218 universal=universal, 

1219 n_variable=n_var, 

1220 n_source=n_source, 

1221 n_expr=n_expr, 

1222 ) 

1223 

1224 quantified_expression.set_ast_link(t_quantified) 

1225 quantified_expression.set_ast_link(t_in) 

1226 quantified_expression.set_ast_link(t_arrow) 

1227 

1228 return quantified_expression 

1229 

1230 def parse_conditional_expression(self, scope): 

1231 # lobster-trace: LRM.Conditional_Expression 

1232 # lobster-trace: LRM.Restricted_Null 

1233 assert isinstance(scope, ast.Scope) 

1234 

1235 self.match_kw("if") 

1236 t_if = self.ct 

1237 if_cond = self.parse_expression(scope) 

1238 self.match_kw("then") 

1239 t_then = self.ct 

1240 if_expr = self.parse_expression(scope) 

1241 if if_expr.typ is None: 

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

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

1244 

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

1246 if_action.set_ast_link(t_if) 

1247 if_action.set_ast_link(t_then) 

1248 

1249 while self.peek_kw("elsif"): 

1250 self.match_kw("elsif") 

1251 t_elsif = self.ct 

1252 elsif_cond = self.parse_expression(scope) 

1253 self.match_kw("then") 

1254 t_then = self.ct 

1255 elsif_expr = self.parse_expression(scope) 

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

1257 elsif_action.set_ast_link(t_elsif) 

1258 elsif_action.set_ast_link(t_then) 

1259 rv.add_elsif(self.mh, elsif_action) 

1260 

1261 self.match_kw("else") 

1262 rv.set_ast_link(self.ct) 

1263 else_expr = self.parse_expression(scope) 

1264 rv.set_else_part(self.mh, else_expr) 

1265 

1266 return rv 

1267 

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

1269 # lobster-trace: LRM.Builtin_Functions 

1270 # lobster-trace: LRM.Builtin_Type_Conversion_Functions 

1271 assert isinstance(scope, ast.Scope) 

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

1273 assert isinstance(t_name, Token) 

1274 

1275 # Parse the arguments. 

1276 parameters = [] 

1277 n_name.set_ast_link(self.ct) 

1278 self.match("BRA") 

1279 n_name.set_ast_link(self.ct) 

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

1281 exp = self.parse_expression(scope) 

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

1283 exp.set_ast_link(self.ct) 

1284 parameters.append(exp) 

1285 

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

1287 self.match("COMMA") 

1288 n_name.set_ast_link(self.ct) 

1289 else: 

1290 break 

1291 self.match("KET") 

1292 n_name.set_ast_link(self.ct) 

1293 

1294 # Enforce arity 

1295 if isinstance(n_name, ast.Builtin_Function): 

1296 required_arity = n_name.arity 

1297 precise = not n_name.arity_at_least 

1298 else: 

1299 required_arity = 1 

1300 precise = True 

1301 

1302 if precise: 

1303 if required_arity != len(parameters): 

1304 self.mh.error( 

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

1306 ) 

1307 else: 

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

1309 self.mh.error( 

1310 t_name.location, 

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

1312 ) 

1313 

1314 # Enforce types 

1315 if n_name.name == "len": 

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

1317 return ast.Unary_Expression( 

1318 mh=self.mh, 

1319 location=t_name.location, 

1320 typ=self.builtin_int, 

1321 operator=ast.Unary_Operator.STRING_LENGTH, 

1322 n_operand=parameters[0], 

1323 ) 

1324 else: 

1325 return ast.Unary_Expression( 

1326 mh=self.mh, 

1327 location=t_name.location, 

1328 typ=self.builtin_int, 

1329 operator=ast.Unary_Operator.ARRAY_LENGTH, 

1330 n_operand=parameters[0], 

1331 ) 

1332 

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

1334 return ast.Binary_Expression( 

1335 mh=self.mh, 

1336 location=t_name.location, 

1337 typ=self.builtin_bool, 

1338 operator=( 

1339 ast.Binary_Operator.STRING_STARTSWITH 

1340 if "startswith" in n_name.name 

1341 else ast.Binary_Operator.STRING_ENDSWITH 

1342 ), 

1343 n_lhs=parameters[0], 

1344 n_rhs=parameters[1], 

1345 ) 

1346 

1347 elif n_name.name == "matches": 

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

1349 try: 

1350 # lobster-trace: LRM.Static_Regular_Expression 

1351 # scope is None on purpose to enforce static context 

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

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

1354 re.compile(value.value) 

1355 except re.error as err: 

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

1357 return ast.Binary_Expression( 

1358 mh=self.mh, 

1359 location=t_name.location, 

1360 typ=self.builtin_bool, 

1361 operator=ast.Binary_Operator.STRING_REGEX, 

1362 n_lhs=parameters[0], 

1363 n_rhs=parameters[1], 

1364 ) 

1365 

1366 elif n_name.name == "oneof": 

1367 return ast.OneOf_Expression( 

1368 mh=self.mh, 

1369 location=t_name.location, 

1370 typ=self.builtin_bool, 

1371 choices=parameters, 

1372 ) 

1373 

1374 elif isinstance(n_name, ast.Builtin_Numeric_Type): 

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

1376 if isinstance(n_name, ast.Builtin_Integer): 

1377 return ast.Unary_Expression( 

1378 mh=self.mh, 

1379 location=t_name.location, 

1380 typ=self.builtin_int, 

1381 operator=ast.Unary_Operator.CONVERSION_TO_INT, 

1382 n_operand=parameters[0], 

1383 ) 

1384 elif isinstance(n_name, ast.Builtin_Decimal): 

1385 return ast.Unary_Expression( 

1386 mh=self.mh, 

1387 location=t_name.location, 

1388 typ=self.builtin_decimal, 

1389 operator=ast.Unary_Operator.CONVERSION_TO_DECIMAL, 

1390 n_operand=parameters[0], 

1391 ) 

1392 else: 

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

1394 

1395 else: 

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

1397 

1398 def parse_name(self, scope): 

1399 # lobster-trace: LRM.Names 

1400 

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

1402 # 

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

1404 # 

1405 # name ::= qualified_name 

1406 # | name '.' IDENTIFIER 

1407 # | name '[' expression ']' 

1408 # | name '(' parameter_list ')' 

1409 # 

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

1411 

1412 assert isinstance(scope, ast.Scope) 

1413 

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

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

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

1417 # components. However as functions cannot be stored in 

1418 # components the true grammar for function calls is always 

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

1420 

1421 # lobster-trace: LRM.Builtin_Functions 

1422 # lobster-trace: LRM.Builtin_Type_Conversion_Functions 

1423 self.match("IDENTIFIER") 

1424 if self.peek("BRA"): 

1425 # If we follow our name with brackets 

1426 # immediately, we have a builtin function call. 

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

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

1429 self.mh.error( 

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

1431 ) 

1432 else: 

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

1434 

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

1436 # first. 

1437 if isinstance(n_name, ast.Enumeration_Type): 

1438 n_name.set_ast_link(self.ct) 

1439 self.match("DOT") 

1440 n_name.set_ast_link(self.ct) 

1441 self.match("IDENTIFIER") 

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

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

1444 enum_lit.set_ast_link(self.ct) 

1445 return enum_lit 

1446 

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

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

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

1450 n_name, 

1451 ( 

1452 ast.Builtin_Function, 

1453 ast.Builtin_Numeric_Type, 

1454 ast.Composite_Component, 

1455 ast.Quantified_Variable, 

1456 ), 

1457 ): 

1458 self.mh.error( 

1459 self.ct.location, 

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

1461 ) 

1462 

1463 # Right now function calls and type conversions must be 

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

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

1466 # lobster-trace: LRM.Builtin_Functions 

1467 # lobster-trace: LRM.Builtin_Type_Conversion_Functions 

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

1469 

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

1471 

1472 # We now process the potentially recursive part: 

1473 # | name '.' IDENTIFIER 

1474 # | name '[' expression ']' 

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

1476 n_name.set_ast_link(self.ct) 

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

1478 if self.peek("DOT"): 

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

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

1481 ): 

1482 # lobster-trace: LRM.Valid_Index_Prefixes 

1483 self.mh.error( 

1484 n_name.location, 

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

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

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

1488 ) 

1489 

1490 self.match("DOT") 

1491 t_dot = self.ct 

1492 self.match("IDENTIFIER") 

1493 t_field = self.ct 

1494 

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

1496 is_universal = True 

1497 

1498 if is_union_access: 

1499 # lobster-trace: LRM.Union_Type_Field_Access 

1500 # lobster-trace: LRM.Union_Type_Field_Type_Conflict 

1501 # lobster-trace: LRM.Union_Type_Field_Access_Validity 

1502 field_name = t_field.value 

1503 field_map = n_name.typ.get_field_map() 

1504 if field_name not in field_map: 

1505 self.mh.error( 

1506 t_field.location, 

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

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

1509 ) 

1510 info = field_map[field_name] 

1511 if info["n_typ"] is None: 

1512 self.mh.error( 

1513 t_field.location, 

1514 "field %s has conflicting types in" 

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

1516 ) 

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

1518 # lobster-trace: LRM.Union_Type_Partial_Field_Access 

1519 if self.lint_mode and not is_universal: 

1520 self.mh.check( 

1521 t_field.location, 

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

1523 " members of union type %s;" 

1524 " accessing it on other members" 

1525 " returns null" 

1526 % ( 

1527 field_name, 

1528 info["count"], 

1529 info["total"], 

1530 n_name.typ.name, 

1531 ), 

1532 "union_partial_field_access", 

1533 ) 

1534 n_field = info["component"] 

1535 else: 

1536 n_field = n_name.typ.components.lookup( 

1537 self.mh, t_field, ast.Composite_Component 

1538 ) 

1539 

1540 n_field.set_ast_link(t_field) 

1541 n_name = ast.Field_Access_Expression( 

1542 mh=self.mh, 

1543 location=t_field.location, 

1544 n_prefix=n_name, 

1545 n_field=n_field, 

1546 is_union_access=is_union_access, 

1547 is_universal=is_universal, 

1548 ) 

1549 n_name.set_ast_link(t_dot) 

1550 

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

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

1553 self.mh.error( 

1554 n_name.location, 

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

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

1557 ) 

1558 

1559 self.match("S_BRA") 

1560 t_bracket = self.ct 

1561 n_index = self.parse_expression(scope) 

1562 self.match("S_KET") 

1563 a_binary = ast.Binary_Operator.INDEX 

1564 t_bracket.ast_link = a_binary 

1565 self.ct.ast_link = a_binary 

1566 

1567 n_name = ast.Binary_Expression( 

1568 mh=self.mh, 

1569 location=t_bracket.location, 

1570 typ=n_name.typ.element_type, 

1571 operator=a_binary, 

1572 n_lhs=n_name, 

1573 n_rhs=n_index, 

1574 ) 

1575 

1576 return n_name 

1577 

1578 def parse_check_block(self): 

1579 # lobster-trace: LRM.Check_Block 

1580 t_severity = None 

1581 self.match_kw("checks") 

1582 t_checks = self.ct 

1583 self.match("IDENTIFIER") 

1584 # lobster-trace: LRM.Applicable_Types 

1585 # lobster-trace: LRM.Applicable_Components 

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

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

1588 n_check_block.set_ast_link(t_checks) 

1589 n_ctype.set_ast_link(self.ct) 

1590 scope = ast.Scope() 

1591 scope.push(self.stab) 

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

1593 scope.push(n_ctype.components) 

1594 self.match("C_BRA") 

1595 n_check_block.set_ast_link(self.ct) 

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

1597 c_expr = self.parse_expression(scope) 

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

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

1600 

1601 self.match("COMMA") 

1602 t_first_comma = self.ct 

1603 if self.peek("KEYWORD"): 

1604 self.match("KEYWORD") 

1605 t_severity = self.ct 

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

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

1608 c_sev = self.ct.value 

1609 else: 

1610 c_sev = "error" 

1611 

1612 self.match("STRING") 

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

1614 # lobster-trace: LRM.No_Newlines_In_Message 

1615 self.mh.error( 

1616 self.ct.location, 

1617 "error message must not contain a newline", 

1618 fatal=False, 

1619 ) 

1620 t_msg = self.ct 

1621 

1622 has_extrainfo = False 

1623 has_anchor = False 

1624 if self.peek("COMMA"): 

1625 self.match("COMMA") 

1626 t_second_comma = self.ct 

1627 if self.peek("IDENTIFIER"): 

1628 has_anchor = True 

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

1630 has_extrainfo = True 

1631 else: 

1632 self.mh.error( 

1633 self.nt.location, 

1634 "expected either a details string or" 

1635 " identifier to anchor the check message", 

1636 ) 

1637 

1638 if has_extrainfo: 

1639 self.match("STRING") 

1640 t_extrainfo = self.ct 

1641 c_extrainfo = self.ct.value 

1642 

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

1644 self.match("COMMA") 

1645 t_third_comma = self.ct 

1646 has_anchor = True 

1647 

1648 else: 

1649 c_extrainfo = None 

1650 

1651 if has_anchor: 

1652 self.match("IDENTIFIER") 

1653 t_anchor = self.ct 

1654 c_anchor = n_ctype.components.lookup( 

1655 self.mh, self.ct, ast.Composite_Component 

1656 ) 

1657 else: 

1658 c_anchor = None 

1659 

1660 n_check = ast.Check( 

1661 n_type=n_ctype, 

1662 n_expr=c_expr, 

1663 n_anchor=c_anchor, 

1664 severity=c_sev, 

1665 t_message=t_msg, 

1666 extrainfo=c_extrainfo, 

1667 ) 

1668 

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

1670 # pylint: disable=used-before-assignment 

1671 

1672 n_check.set_ast_link(t_first_comma) 

1673 if t_severity: 

1674 n_check.set_ast_link(t_severity) 

1675 n_check.set_ast_link(t_msg) 

1676 if c_extrainfo or c_anchor: 

1677 n_check.set_ast_link(t_second_comma) 

1678 if c_extrainfo: 

1679 n_check.set_ast_link(t_extrainfo) 

1680 if c_anchor: 

1681 c_anchor.set_ast_link(t_anchor) 

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

1683 n_check.set_ast_link(t_third_comma) 

1684 

1685 n_ctype.add_check(n_check) 

1686 n_check_block.add_check(n_check) 

1687 

1688 assert scope.size() == 3 

1689 

1690 self.match("C_KET") 

1691 n_check_block.set_ast_link(self.ct) 

1692 

1693 return n_check_block 

1694 

1695 def parse_section_declaration(self): 

1696 # lobster-trace: LRM.Section_Declaration 

1697 self.match_kw("section") 

1698 t_section = self.ct 

1699 self.match("STRING") 

1700 sec = ast.Section( 

1701 name=self.ct.value, 

1702 location=self.ct.location, 

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

1704 ) 

1705 sec.set_ast_link(self.ct) 

1706 sec.set_ast_link(t_section) 

1707 self.section.append(sec) 

1708 self.match("C_BRA") 

1709 sec.set_ast_link(self.ct) 

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

1711 self.parse_trlc_entry() 

1712 self.match("C_KET") 

1713 sec.set_ast_link(self.ct) 

1714 self.section.pop() 

1715 

1716 def parse_boolean(self): 

1717 # lobster-trace: LRM.Boolean_Values 

1718 self.match("KEYWORD") 

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

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

1721 else: 

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

1723 

1724 def parse_value(self, typ): 

1725 # lobster-trace: LRM.Tuple_Syntax_Correct_Form 

1726 assert isinstance(typ, ast.Type) 

1727 

1728 if isinstance(typ, ast.Builtin_Numeric_Type): 

1729 # lobster-trace: LRM.Integer_Values 

1730 # lobster-trace: LRM.Decimal_Values 

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

1732 self.match("OPERATOR") 

1733 t_op = self.ct 

1734 e_op = ( 

1735 ast.Unary_Operator.PLUS 

1736 if t_op.value == "+" 

1737 else ast.Unary_Operator.MINUS 

1738 ) 

1739 t_op.ast_link = e_op 

1740 else: 

1741 t_op = None 

1742 

1743 if isinstance(typ, ast.Builtin_Decimal): 

1744 self.match("DECIMAL") 

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

1746 rv.set_ast_link(self.ct) 

1747 elif isinstance(typ, ast.Builtin_Integer): 

1748 self.match("INTEGER") 

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

1750 rv.set_ast_link(self.ct) 

1751 else: 

1752 assert False 

1753 

1754 if t_op: 

1755 rv = ast.Unary_Expression( 

1756 mh=self.mh, 

1757 location=t_op.location, 

1758 typ=rv.typ, 

1759 operator=e_op, 

1760 n_operand=rv, 

1761 ) 

1762 

1763 return rv 

1764 

1765 elif isinstance(typ, ast.Builtin_Markup_String): 

1766 # lobster-trace: LRM.Markup_String_Values 

1767 return self.parse_markup_string() 

1768 

1769 elif isinstance(typ, ast.Builtin_String): 

1770 # lobster-trace: LRM.String_Values 

1771 self.match("STRING") 

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

1773 rv.set_ast_link(self.ct) 

1774 return rv 

1775 

1776 elif isinstance(typ, ast.Builtin_Boolean): 

1777 rv = self.parse_boolean() 

1778 rv.set_ast_link(self.ct) 

1779 return rv 

1780 

1781 elif isinstance(typ, ast.Array_Type): 

1782 self.match("S_BRA") 

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

1784 rv.set_ast_link(self.ct) 

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

1786 array_elem = self.parse_value(typ.element_type) 

1787 rv.append(array_elem) 

1788 if self.peek("COMMA"): 

1789 self.match("COMMA") 

1790 rv.set_ast_link(self.ct) 

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

1792 break 

1793 else: 

1794 self.mh.error( 

1795 self.ct.location, 

1796 "comma separating array elements is missing", 

1797 fatal=False, 

1798 ) 

1799 

1800 self.match("S_KET") 

1801 rv.set_ast_link(self.ct) 

1802 

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

1804 self.mh.error( 

1805 self.ct.location, 

1806 "this array requires at least %u elements " 

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

1808 fatal=False, 

1809 ) 

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

1811 self.mh.error( 

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

1813 "this array requires at most %u elements " 

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

1815 fatal=False, 

1816 ) 

1817 

1818 return rv 

1819 

1820 elif isinstance(typ, ast.Enumeration_Type): 

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

1822 enum.set_ast_link(self.ct) 

1823 if enum != typ: 

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

1825 self.match("DOT") 

1826 enum.set_ast_link(self.ct) 

1827 self.match("IDENTIFIER") 

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

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

1830 

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

1832 self.match("IDENTIFIER") 

1833 t_name = self.ct 

1834 if self.peek("DOT"): 

1835 self.match("DOT") 

1836 t_dot = self.ct 

1837 self.match("IDENTIFIER") 

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

1839 the_pkg.set_ast_link(t_name) 

1840 the_pkg.set_ast_link(t_dot) 

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

1842 self.mh.error( 

1843 self.ct.location, "package must be imported before use" 

1844 ) 

1845 t_name = self.ct 

1846 else: 

1847 the_pkg = self.cu.package 

1848 

1849 rv = ast.Record_Reference( 

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

1851 ) 

1852 rv.set_ast_link(t_name) 

1853 

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

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

1856 rv.resolve_references(self.mh) 

1857 

1858 return rv 

1859 

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

1861 # lobster-trace: LRM.Tuple_Separator_Form 

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

1863 

1864 next_is_optional = False 

1865 for n_item in typ.iter_sequence(): 

1866 if isinstance(n_item, ast.Composite_Component): 

1867 if next_is_optional and n_item.optional: 

1868 break 

1869 value = self.parse_value(n_item.n_typ) 

1870 rv.assign(n_item.name, value) 

1871 

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

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

1874 self.match(n_item.token.kind) 

1875 n_item.set_ast_link(self.ct) 

1876 else: 

1877 next_is_optional = True 

1878 

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

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

1881 self.match("IDENTIFIER") 

1882 n_item.set_ast_link(self.ct) 

1883 else: 

1884 next_is_optional = True 

1885 

1886 else: 

1887 assert False 

1888 

1889 return rv 

1890 

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

1892 # lobster-trace: LRM.Tuple_Generic_Form 

1893 self.match("BRA") 

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

1895 rv.set_ast_link(self.ct) 

1896 

1897 first = True 

1898 for n_field in typ.iter_sequence(): 

1899 if first: 

1900 first = False 

1901 else: 

1902 self.match("COMMA") 

1903 rv.set_ast_link(self.ct) 

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

1905 

1906 self.match("KET") 

1907 rv.set_ast_link(self.ct) 

1908 return rv 

1909 

1910 else: 

1911 self.mh.ice_loc( 

1912 self.ct.location, 

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

1914 ) 

1915 

1916 def parse_markup_string(self): 

1917 # lobster-trace: LRM.Markup_String_Values 

1918 self.match("STRING") 

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

1920 mpar = Markup_Parser(self, rv) 

1921 mpar.parse_all_references() 

1922 return rv 

1923 

1924 def parse_record_object_declaration(self): 

1925 # lobster-trace: LRM.Section_Declaration 

1926 # lobster-trace: LRM.Record_Object_Declaration 

1927 # lobster-trace: LRM.Valid_Record_Types 

1928 # lobster-trace: LRM.Valid_Components 

1929 # lobster-trace: LRM.Valid_Enumeration_Literals 

1930 # lobster-trace: LRM.Mandatory_Components 

1931 # lobster-trace: LRM.Evaluation_Of_Checks 

1932 # lobster-trace: LRM.Single_Value_Assignment 

1933 

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

1935 r_typ.set_ast_link(self.ct) 

1936 # lobster-trace: LRM.Abstract_Types 

1937 if r_typ.is_abstract: 

1938 self.mh.error( 

1939 self.ct.location, 

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

1941 ) 

1942 

1943 self.match("IDENTIFIER") 

1944 obj = ast.Record_Object( 

1945 name=self.ct.value, 

1946 location=self.ct.location, 

1947 n_typ=r_typ, 

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

1949 n_package=self.cu.package, 

1950 ) 

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

1952 obj.set_ast_link(self.ct) 

1953 

1954 self.match("C_BRA") 

1955 obj.set_ast_link(self.ct) 

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

1957 self.match("IDENTIFIER") 

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

1959 if obj.is_component_implicit_null(comp): 

1960 self.mh.error( 

1961 self.ct.location, 

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

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

1964 ) 

1965 comp.set_ast_link(self.ct) 

1966 if r_typ.is_frozen(comp): 

1967 self.mh.error( 

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

1969 ) 

1970 self.match("ASSIGN") 

1971 comp.set_ast_link(self.ct) 

1972 value = self.parse_value(comp.n_typ) 

1973 if not self.ct.ast_link: 

1974 value.set_ast_link(self.ct) 

1975 obj.assign(comp, value) 

1976 

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

1978 for comp in r_typ.all_components(): 

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

1980 if r_typ.is_frozen(comp): 

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

1982 elif not comp.optional: 

1983 self.mh.error( 

1984 obj.location, 

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

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

1987 ) 

1988 

1989 self.match("C_KET") 

1990 obj.set_ast_link(self.ct) 

1991 

1992 return obj 

1993 

1994 def parse_trlc_entry(self): 

1995 # lobster-trace: LRM.TRLC_File 

1996 if self.peek_kw("section"): 

1997 self.parse_section_declaration() 

1998 else: 

1999 self.cu.add_item(self.parse_record_object_declaration()) 

2000 

2001 def parse_preamble(self, kind): 

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

2003 # lobster-trace: LRM.Layout 

2004 # lobster-trace: LRM.Preamble 

2005 

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

2007 # needed 

2008 self.match_kw("package") 

2009 t_pkg = self.ct 

2010 self.match("IDENTIFIER") 

2011 

2012 if kind == "rsl": 

2013 declare_package = True 

2014 else: 

2015 # lobster-trace: LRM.Late_Package_Declarations 

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

2017 

2018 if declare_package: 

2019 # lobster-trace: LRM.Package_Declaration 

2020 pkg = ast.Package( 

2021 name=self.ct.value, 

2022 location=self.ct.location, 

2023 builtin_stab=self.stab, 

2024 declared_late=kind == "trlc", 

2025 ) 

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

2027 else: 

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

2029 

2030 pkg.set_ast_link(t_pkg) 

2031 pkg.set_ast_link(self.ct) 

2032 

2033 # lobster-trace: LRM.Current_Package 

2034 self.cu.set_package(pkg) 

2035 

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

2037 

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

2039 # lobster-trace: LRM.Import_Visibility 

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

2041 while self.peek_kw("import"): 

2042 self.match_kw("import") 

2043 pkg.set_ast_link(self.ct) 

2044 self.match("IDENTIFIER") 

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

2046 

2047 def parse_rsl_file(self): 

2048 # lobster-trace: LRM.RSL_File 

2049 assert self.cu.package is not None 

2050 

2051 ok = True 

2052 while not self.peek_eof(): 

2053 try: 

2054 if self.peek_kw("checks"): 

2055 self.cu.add_item(self.parse_check_block()) 

2056 else: 

2057 self.cu.add_item(self.parse_type_declaration()) 

2058 except TRLC_Error as err: 

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

2060 raise 

2061 

2062 ok = False 

2063 

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

2065 # relevant keyword 

2066 self.skip_until_newline() 

2067 while not self.peek_eof(): 

2068 if ( 

2069 self.peek_kw("checks") 

2070 or self.peek_kw("type") 

2071 or self.peek_kw("abstract") 

2072 or self.peek_kw("final") 

2073 or self.peek_kw("tuple") 

2074 or self.peek_kw("enum") 

2075 ): 

2076 break 

2077 self.advance() 

2078 self.skip_until_newline() 

2079 

2080 self.match_eof() 

2081 

2082 for tok in self.lexer.tokens: 

2083 if tok.kind == "COMMENT": 

2084 self.cu.package.set_ast_link(tok) 

2085 

2086 return ok 

2087 

2088 def parse_trlc_file(self): 

2089 # lobster-trace: LRM.TRLC_File 

2090 assert self.cu.package is not None 

2091 

2092 ok = True 

2093 

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

2095 try: 

2096 self.parse_trlc_entry() 

2097 except TRLC_Error as err: 

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

2099 raise 

2100 

2101 ok = False 

2102 

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

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

2105 # EOF 

2106 self.skip_until_newline() 

2107 while not self.peek_eof(): 

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

2109 break 

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

2111 pass 

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

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

2114 if isinstance(n_sym, ast.Package): 

2115 break 

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

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

2118 self.mh, self.nt.value 

2119 ) 

2120 if isinstance(n_sym, ast.Record_Type): 

2121 break 

2122 self.advance() 

2123 self.skip_until_newline() 

2124 

2125 self.match_eof() 

2126 

2127 for tok in self.lexer.tokens: 

2128 if tok.kind == "COMMENT": 

2129 self.cu.package.set_ast_link(tok) 

2130 

2131 return ok