Coverage for trlc/parser.py: 96%

1123 statements  

« prev     ^ index     » next       coverage.py v7.10.7, created at 2026-05-21 07:21 +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(self.source_location(start_line, 

75 start_col, 

76 start_pos, 

77 start_pos + 1), 

78 "cannot nest reference lists") 

79 else: 

80 self.in_reflist = True 

81 

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

83 kind = "REFLIST_END" 

84 self.advance() 

85 if self.in_reflist: 

86 self.in_reflist = False 

87 else: 

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

89 start_col, 

90 start_pos, 

91 start_pos + 1), 

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

93 

94 elif not self.in_reflist: 

95 kind = "CHARACTER" 

96 

97 elif self.cc == ",": 

98 kind = "REFLIST_COMMA" 

99 

100 elif self.cc == ".": 

101 kind = "REFLIST_DOT" 

102 

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

104 kind = "REFLIST_IDENTIFIER" 

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

106 self.nc == "_"): 

107 self.advance() 

108 

109 else: 

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

111 start_col, 

112 start_pos, 

113 start_pos), 

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

115 

116 loc = self.source_location(start_line, 

117 start_col, 

118 start_pos, 

119 self.lexpos) 

120 

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

122 return Markup_Token(loc, 

123 kind, 

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

125 

126 

127class Parser_Base: 

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

129 assert isinstance(mh, Message_Handler) 

130 assert isinstance(lexer, Lexer_Base) 

131 assert isinstance(eoc_name, str) 

132 assert isinstance(token_map, dict) 

133 assert isinstance(keywords, frozenset) 

134 self.mh = mh 

135 self.lexer = lexer 

136 

137 self.eoc_name = eoc_name 

138 self.language_tokens = token_map 

139 self.language_keywords = keywords 

140 

141 self.ct = None 

142 self.nt = None 

143 self.advance() 

144 

145 def advance(self): 

146 # lobster-trace: LRM.Comments 

147 self.ct = self.nt 

148 while True: 

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

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

151 break 

152 

153 def skip_until_newline(self): 

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

155 return 

156 current_line = self.ct.location.line_no 

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

158 self.advance() 

159 

160 def peek(self, kind): 

161 assert kind in self.language_tokens, \ 

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

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

164 

165 def peek_eof(self): 

166 return self.nt is None 

167 

168 def peek_kw(self, value): 

169 assert value in self.language_keywords, \ 

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

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

172 

173 def match(self, kind): 

174 # lobster-trace: LRM.Matching_Value_Types 

175 

176 assert kind in self.language_tokens, \ 

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

178 if self.nt is None: 

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

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

181 "expected %s, encountered %s instead" % 

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

183 else: 

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

185 "expected %s, encountered %s instead" % 

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

187 elif self.nt.kind != kind: 

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

189 "expected %s, encountered %s instead" % 

190 (self.language_tokens[kind], 

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

192 self.advance() 

193 

194 def match_eof(self): 

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

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

197 "expected %s, encountered %s instead" % 

198 (self.eoc_name, 

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

200 

201 def match_kw(self, value): 

202 assert value in self.language_keywords, \ 

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

204 if self.nt is None: 

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

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

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

208 (value, self.eoc_name)) 

209 else: 

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

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

212 (value, self.eoc_name)) 

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

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

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

216 (value, 

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

218 elif self.nt.value != value: 

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

220 "expected keyword %s," 

221 " encountered keyword %s instead" % 

222 (value, self.nt.value)) 

223 self.advance() 

224 

225 

226class Markup_Parser(Parser_Base): 

227 def __init__(self, parent, literal): 

228 assert isinstance(parent, Parser) 

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

230 eoc_name = "end-of-string", 

231 token_map = Markup_Token.KIND, 

232 keywords = frozenset()) 

233 self.parent = parent 

234 self.references = literal.references 

235 

236 def parse_all_references(self): 

237 while self.nt: 

238 if self.peek("CHARACTER"): 

239 self.advance() 

240 else: 

241 self.parse_ref_list() 

242 self.match_eof() 

243 return self.references 

244 

245 def parse_ref_list(self): 

246 self.match("REFLIST_BEGIN") 

247 self.parse_qualified_name() 

248 while self.peek("REFLIST_COMMA"): 

249 self.match("REFLIST_COMMA") 

250 self.parse_qualified_name() 

251 self.match("REFLIST_END") 

252 

253 def parse_qualified_name(self): 

254 # lobster-trace: LRM.Qualified_Name 

255 # lobster-trace: LRM.Valid_Qualifier 

256 # lobster-trace: LRM.Valid_Name 

257 # lobster-trace: LRM.Markup_String_Resolution 

258 # lobster-trace: LRM.Markup_String_Types 

259 

260 self.match("REFLIST_IDENTIFIER") 

261 if self.peek("REFLIST_DOT"): 

262 package = self.parent.stab.lookup_direct( 

263 mh = self.mh, 

264 name = self.ct.value, 

265 error_location = self.ct.location, 

266 required_subclass = ast.Package) 

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

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

269 "package must be imported before use") 

270 

271 self.match("REFLIST_DOT") 

272 self.match("REFLIST_IDENTIFIER") 

273 else: 

274 package = self.parent.cu.package 

275 

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

277 name = self.ct.value, 

278 typ = None, 

279 package = package) 

280 self.references.append(ref) 

281 

282 

283class Parser(Parser_Base): 

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

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

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

287 

288 def __init__(self, 

289 mh, 

290 stab, 

291 file_name, 

292 lint_mode, 

293 error_recovery, 

294 primary_file=True, 

295 lexer=None): 

296 assert isinstance(mh, Message_Handler) 

297 assert isinstance(stab, ast.Symbol_Table) 

298 assert isinstance(file_name, str) 

299 assert isinstance(lint_mode, bool) 

300 assert isinstance(error_recovery, bool) 

301 assert isinstance(primary_file, bool) 

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

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

304 super().__init__(mh, lexer, 

305 eoc_name = "end-of-file", 

306 token_map = Token.KIND, 

307 keywords = TRLC_Lexer.KEYWORDS) 

308 else: 

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

310 eoc_name = "end-of-file", 

311 token_map = Token.KIND, 

312 keywords = TRLC_Lexer.KEYWORDS) 

313 

314 self.lint_mode = lint_mode 

315 self.error_recovery = error_recovery 

316 

317 self.stab = stab 

318 self.cu = ast.Compilation_Unit(file_name) 

319 

320 self.primary = primary_file 

321 self.secondary = False 

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

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

324 # was selected by dependency analysis. 

325 

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

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

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

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

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

331 

332 self.section = [] 

333 self.default_scope = ast.Scope() 

334 self.default_scope.push(self.stab) 

335 

336 def parse_described_name(self): 

337 # lobster-trace: LRM.Described_Names 

338 # lobster-trace: LRM.Described_Name_Description 

339 self.match("IDENTIFIER") 

340 name = self.ct 

341 

342 if self.peek("STRING"): 

343 self.match("STRING") 

344 t_descr = self.ct 

345 return name, t_descr.value, t_descr 

346 else: 

347 return name, None, None 

348 

349 def parse_qualified_name(self, 

350 scope, 

351 required_subclass=None, 

352 match_ident=True): 

353 # lobster-trace: LRM.Qualified_Name 

354 # lobster-trace: LRM.Valid_Qualifier 

355 # lobster-trace: LRM.Valid_Name 

356 assert isinstance(scope, ast.Scope) 

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

358 assert isinstance(match_ident, bool) 

359 

360 if match_ident: 

361 self.match("IDENTIFIER") 

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

363 sym.set_ast_link(self.ct) 

364 

365 if isinstance(sym, ast.Package): 

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

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

368 "package must be imported before use") 

369 self.match("DOT") 

370 sym.set_ast_link(self.ct) 

371 self.match("IDENTIFIER") 

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

373 else: 

374 # Easiest way to generate the correct error message 

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

376 

377 def parse_type_declaration(self): 

378 # lobster-trace: LRM.Type_Declarations 

379 if self.peek_kw("enum"): 

380 n_item = self.parse_enum_declaration() 

381 elif self.peek_kw("tuple"): 

382 n_item = self.parse_tuple_declaration() 

383 else: 

384 n_item = self.parse_record_declaration() 

385 assert isinstance(n_item, ast.Concrete_Type) 

386 return n_item 

387 

388 def parse_enum_declaration(self): 

389 # lobster-trace: LRM.Enumeration_Declaration 

390 self.match_kw("enum") 

391 t_enum = self.ct 

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

393 

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

395 description = description, 

396 location = name.location, 

397 package = self.cu.package) 

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

399 enum.set_ast_link(t_enum) 

400 enum.set_ast_link(name) 

401 if t_description: 

402 enum.set_ast_link(t_description) 

403 

404 self.match("C_BRA") 

405 enum.set_ast_link(self.ct) 

406 empty = True 

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

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

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

410 description = description, 

411 location = name.location, 

412 enum = enum) 

413 lit.set_ast_link(name) 

414 if t_description: 

415 lit.set_ast_link(self.ct) 

416 empty = False 

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

418 self.match("C_KET") 

419 enum.set_ast_link(self.ct) 

420 

421 if empty: 

422 # lobster-trace: LRM.No_Empty_Enumerations 

423 self.mh.error(enum.location, 

424 "empty enumerations are not permitted") 

425 

426 return enum 

427 

428 def parse_tuple_field(self, 

429 n_tuple, 

430 optional_allowed, 

431 optional_reason, 

432 optional_required): 

433 assert isinstance(n_tuple, ast.Tuple_Type) 

434 assert isinstance(optional_allowed, bool) 

435 assert isinstance(optional_reason, str) 

436 assert isinstance(optional_required, bool) 

437 assert optional_allowed or not optional_required 

438 

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

440 

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

442 self.match_kw("optional") 

443 t_optional = self.ct 

444 field_is_optional = True 

445 if not optional_allowed: 

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

447 else: 

448 field_is_optional = False 

449 t_optional = None 

450 

451 # lobster-trace: LRM.Tuple_Field_Types 

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

453 if self.peek("S_BRA"): 

454 # lobster-trace: LRM.union_type 

455 field_type = self.parse_union_type() 

456 else: 

457 field_type = self.parse_qualified_name(self.default_scope, 

458 ast.Type) 

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

460 description = field_description, 

461 location = field_name.location, 

462 member_of = n_tuple, 

463 n_typ = field_type, 

464 optional = field_is_optional) 

465 comp.set_ast_link(field_name) 

466 if t_descr: 

467 comp.set_ast_link(t_descr) 

468 if field_is_optional: 

469 comp.set_ast_link(t_optional) 

470 

471 return comp 

472 

473 def parse_tuple_declaration(self): 

474 # lobster-trace: LRM.Tuple_Declaration 

475 self.match_kw("tuple") 

476 t_tuple = self.ct 

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

478 

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

480 description = description, 

481 location = name.location, 

482 package = self.cu.package) 

483 

484 n_tuple.set_ast_link(t_tuple) 

485 n_tuple.set_ast_link(name) 

486 if t_descr: 

487 n_tuple.set_ast_link(t_descr) 

488 self.match("C_BRA") 

489 n_tuple.set_ast_link(self.ct) 

490 

491 n_field = self.parse_tuple_field( 

492 n_tuple, 

493 optional_allowed = False, 

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

495 optional_required = False) 

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

497 

498 has_separators = False 

499 optional_required = False 

500 separator_allowed = True 

501 

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

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

504 has_separators = True 

505 self.match_kw("separator") 

506 t_sep = self.ct 

507 if not separator_allowed: 

508 # lobster-trace: LRM.Tuple_Separators_All_Or_None 

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

510 "either all fields must be separated," 

511 " or none") 

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

513 self.peek("AT") or \ 

514 self.peek("COLON") or \ 

515 self.peek("SEMICOLON"): 

516 self.advance() 

517 sep = ast.Separator(self.ct) 

518 sep.set_ast_link(t_sep) 

519 sep.set_ast_link(self.ct) 

520 n_tuple.add_separator(sep) 

521 else: 

522 separator_allowed = False 

523 # lobster-trace: LRM.Tuple_Optional_Requires_Separators 

524 n_field = self.parse_tuple_field( 

525 n_tuple, 

526 optional_allowed = has_separators, 

527 optional_reason = ("optional only permitted in tuples" 

528 " with separators"), 

529 optional_required = optional_required) 

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

531 # lobster-trace: LRM.Tuple_Optional_Fields 

532 optional_required |= n_field.optional 

533 

534 self.match("C_KET") 

535 n_tuple.set_ast_link(self.ct) 

536 

537 # Final check to ban tuples with separators containing other 

538 # tuples. 

539 if has_separators: 

540 # lobster-trace: LRM.Restricted_Tuple_Nesting 

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

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

543 n_field.n_typ.has_separators(): 

544 self.mh.error( 

545 n_field.location, 

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

547 " may not contain another tuple with separators" 

548 % n_tuple.name) 

549 

550 # Late registration to avoid recursion in tuples 

551 # lobster-trace: LRM.Tuple_Field_Types 

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

553 

554 return n_tuple 

555 

556 def parse_union_type(self): 

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

558 

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

560 Returns an ast.Union_Type node. 

561 """ 

562 # lobster-trace: LRM.union_type 

563 # lobster-trace: LRM.Union_Type_No_Duplicates 

564 # lobster-trace: LRM.Union_Type_Record_Types_Only 

565 self.match("S_BRA") 

566 t_s_bra = self.ct 

567 

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

569 first_type = self.parse_qualified_name(self.default_scope, 

570 ast.Record_Type) 

571 first_type.set_ast_link(self.ct) 

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

573 

574 while self.peek("COMMA"): 

575 self.match("COMMA") 

576 next_type = self.parse_qualified_name( 

577 self.default_scope, 

578 ast.Record_Type) 

579 next_type.set_ast_link(self.ct) 

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

581 

582 self.match("S_KET") 

583 t_s_ket = self.ct 

584 

585 seen = {} 

586 for t, loc in union_type_entries: 

587 fqn = t.fully_qualified_name() 

588 if fqn in seen: 

589 self.mh.error(loc, 

590 "duplicate type %s in union" % t.name, 

591 fatal=False) 

592 else: 

593 seen[fqn] = loc 

594 

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

596 c_typ = ast.Union_Type( 

597 location = t_s_bra.location, 

598 types = union_types) 

599 c_typ.set_ast_link(t_s_bra) 

600 c_typ.set_ast_link(t_s_ket) 

601 return c_typ 

602 

603 def parse_record_component(self, n_record): 

604 assert isinstance(n_record, ast.Record_Type) 

605 

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

607 t_optional = None 

608 c_optional = False 

609 if self.peek_kw("optional"): 

610 self.match_kw("optional") 

611 t_optional = self.ct 

612 c_optional = True 

613 

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

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

616 if self.peek("S_BRA"): 

617 c_typ = self.parse_union_type() 

618 else: 

619 c_typ = self.parse_qualified_name(self.default_scope, 

620 ast.Type) 

621 c_typ.set_ast_link(self.ct) 

622 

623 if self.peek("S_BRA"): 

624 self.match("S_BRA") 

625 t_s_bra = self.ct 

626 self.match("INTEGER") 

627 t_lo = self.ct 

628 a_lo = self.ct.value 

629 loc_lo = self.ct.location 

630 self.match("RANGE") 

631 t_range = self.ct 

632 a_loc = self.ct.location 

633 a_hi = None 

634 if self.peek("INTEGER"): 

635 self.match("INTEGER") 

636 a_hi = self.ct.value 

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

638 self.match("OPERATOR") 

639 else: 

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

641 "expected INTEGER or * for upper bound") 

642 t_hi = self.ct 

643 loc_hi = self.ct.location 

644 self.match("S_KET") 

645 t_s_ket = self.ct 

646 c_typ = ast.Array_Type(location = a_loc, 

647 element_type = c_typ, 

648 lower_bound = a_lo, 

649 upper_bound = a_hi, 

650 loc_lower = loc_lo, 

651 loc_upper = loc_hi) 

652 c_typ.set_ast_link(t_s_bra) 

653 c_typ.set_ast_link(t_lo) 

654 c_typ.set_ast_link(t_range) 

655 c_typ.set_ast_link(t_hi) 

656 c_typ.set_ast_link(t_s_ket) 

657 

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

659 description = c_descr, 

660 location = c_name.location, 

661 member_of = n_record, 

662 n_typ = c_typ, 

663 optional = c_optional) 

664 c_comp.set_ast_link(c_name) 

665 if t_descr: 

666 c_comp.set_ast_link(t_descr) 

667 if c_optional: 

668 c_comp.set_ast_link(t_optional) 

669 

670 return c_comp 

671 

672 def parse_record_declaration(self): 

673 t_abstract = None 

674 t_final = None 

675 is_abstract = False 

676 is_final = False 

677 if self.peek_kw("abstract"): 

678 self.match_kw("abstract") 

679 t_abstract = self.ct 

680 is_abstract = True 

681 elif self.peek_kw("final"): 

682 self.match_kw("final") 

683 t_final = self.ct 

684 is_final = True 

685 

686 self.match_kw("type") 

687 t_type = self.ct 

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

689 

690 if self.peek_kw("extends"): 

691 self.match_kw("extends") 

692 t_extends = self.ct 

693 root_record = self.parse_qualified_name(self.default_scope, 

694 ast.Record_Type) 

695 root_record.set_ast_link(t_extends) 

696 root_record.set_ast_link(self.ct) 

697 else: 

698 root_record = None 

699 

700 if self.lint_mode and \ 

701 root_record and root_record.is_final and \ 

702 not is_final: 

703 self.mh.check(name.location, 

704 "consider clarifying that this record is final", 

705 "clarify_final", 

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

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

708 "clarifies this to casual readers." % 

709 root_record.fully_qualified_name())) 

710 

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

712 description = description, 

713 location = name.location, 

714 package = self.cu.package, 

715 n_parent = root_record, 

716 is_abstract = is_abstract) 

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

718 if is_abstract: 

719 record.set_ast_link(t_abstract) 

720 if is_final: 

721 record.set_ast_link(t_final) 

722 record.set_ast_link(t_type) 

723 record.set_ast_link(name) 

724 if t_description: 

725 record.set_ast_link(t_description) 

726 

727 self.match("C_BRA") 

728 record.set_ast_link(self.ct) 

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

730 if self.peek_kw("freeze"): 

731 self.match_kw("freeze") 

732 t_freeze = self.ct 

733 self.match("IDENTIFIER") 

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

735 self.ct, 

736 ast.Composite_Component) 

737 if record.is_frozen(n_comp): 

738 n_value = record.get_freezing_expression(n_comp) 

739 self.mh.error( 

740 self.ct.location, 

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

742 (n_comp.name, 

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

744 n_comp.set_ast_link(t_freeze) 

745 n_comp.set_ast_link(self.ct) 

746 self.match("ASSIGN") 

747 n_comp.set_ast_link(self.ct) 

748 n_value = self.parse_value(n_comp.n_typ) 

749 n_value.set_ast_link(self.ct) 

750 

751 record.frozen[n_comp.name] = n_value 

752 

753 else: 

754 n_comp = self.parse_record_component(record) 

755 if record.is_final: 

756 self.mh.error(n_comp.location, 

757 "cannot declare new components in" 

758 " final record type") 

759 else: 

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

761 

762 self.match("C_KET") 

763 record.set_ast_link(self.ct) 

764 

765 # Finally mark record final if applicable 

766 if is_final: 

767 record.is_final = True 

768 

769 return record 

770 

771 def parse_expression(self, scope): 

772 # lobster-trace: LRM.Expression 

773 assert isinstance(scope, ast.Scope) 

774 

775 n_lhs = self.parse_relation(scope) 

776 

777 if self.peek_kw("and"): 

778 while self.peek_kw("and"): 

779 self.match_kw("and") 

780 t_op = self.ct 

781 a_op = ast.Binary_Operator.LOGICAL_AND 

782 t_op.ast_link = a_op 

783 n_rhs = self.parse_relation(scope) 

784 n_lhs = ast.Binary_Expression( 

785 mh = self.mh, 

786 location = t_op.location, 

787 typ = self.builtin_bool, 

788 operator = a_op, 

789 n_lhs = n_lhs, 

790 n_rhs = n_rhs) 

791 

792 elif self.peek_kw("or"): 

793 while self.peek_kw("or"): 

794 self.match_kw("or") 

795 t_op = self.ct 

796 a_op = ast.Binary_Operator.LOGICAL_OR 

797 t_op.ast_link = a_op 

798 n_rhs = self.parse_relation(scope) 

799 n_lhs = 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("xor"): 

808 self.match_kw("xor") 

809 t_op = self.ct 

810 a_op = ast.Binary_Operator.LOGICAL_XOR 

811 t_op.ast_link = a_op 

812 n_rhs = self.parse_relation(scope) 

813 n_lhs = ast.Binary_Expression( 

814 mh = self.mh, 

815 location = t_op.location, 

816 typ = self.builtin_bool, 

817 operator = a_op, 

818 n_lhs = n_lhs, 

819 n_rhs = n_rhs) 

820 

821 elif self.peek_kw("implies"): 

822 self.match_kw("implies") 

823 t_op = self.ct 

824 a_op = ast.Binary_Operator.LOGICAL_IMPLIES 

825 t_op.ast_link = a_op 

826 n_rhs = self.parse_relation(scope) 

827 n_lhs = ast.Binary_Expression( 

828 mh = self.mh, 

829 location = t_op.location, 

830 typ = self.builtin_bool, 

831 operator = a_op, 

832 n_lhs = n_lhs, 

833 n_rhs = n_rhs) 

834 

835 return n_lhs 

836 

837 def parse_relation(self, scope): 

838 # lobster-trace: LRM.Relation 

839 # lobster-trace: LRM.Operators 

840 assert isinstance(scope, ast.Scope) 

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

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

843 "<" : ast.Binary_Operator.COMP_LT, 

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

845 ">" : ast.Binary_Operator.COMP_GT, 

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

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

848 

849 n_lhs = self.parse_simple_expression(scope) 

850 

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

852 self.nt.value in Parser.COMPARISON_OPERATOR: 

853 self.match("OPERATOR") 

854 t_op = self.ct 

855 a_op = relop_mapping[t_op.value] 

856 t_op.ast_link = a_op 

857 n_rhs = self.parse_simple_expression(scope) 

858 return ast.Binary_Expression( 

859 mh = self.mh, 

860 location = t_op.location, 

861 typ = self.builtin_bool, 

862 operator = a_op, 

863 n_lhs = n_lhs, 

864 n_rhs = n_rhs) 

865 

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

867 if self.peek_kw("not"): 

868 self.match_kw("not") 

869 t_not = self.ct 

870 else: 

871 t_not = None 

872 

873 self.match_kw("in") 

874 t_in = self.ct 

875 

876 n_a = self.parse_simple_expression(scope) 

877 t_n_a = self.ct 

878 if self.peek("RANGE"): 

879 self.match("RANGE") 

880 t_range = self.ct 

881 n_b = self.parse_simple_expression(scope) 

882 n_b.set_ast_link(self.ct) 

883 n_a.set_ast_link(t_n_a) 

884 rv = ast.Range_Test( 

885 mh = self.mh, 

886 location = t_in.location, 

887 typ = self.builtin_bool, 

888 n_lhs = n_lhs, 

889 n_lower = n_a, 

890 n_upper = n_b) 

891 rv.set_ast_link(t_range) 

892 rv.set_ast_link(t_in) 

893 

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

895 rv = ast.Binary_Expression( 

896 mh = self.mh, 

897 location = t_in.location, 

898 typ = self.builtin_bool, 

899 operator = ast.Binary_Operator.STRING_CONTAINS, 

900 n_lhs = n_lhs, 

901 n_rhs = n_a) 

902 rv.set_ast_link(t_in) 

903 

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

905 a_op = ast.Binary_Operator.ARRAY_CONTAINS 

906 t_in.ast_link = a_op 

907 rv = ast.Binary_Expression( 

908 mh = self.mh, 

909 location = t_in.location, 

910 typ = self.builtin_bool, 

911 operator = a_op, 

912 n_lhs = n_lhs, 

913 n_rhs = n_a) 

914 

915 else: 

916 self.mh.error( 

917 n_a.location, 

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

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

920 

921 if t_not is not None: 

922 a_unary_op = ast.Unary_Operator.LOGICAL_NOT 

923 t_not.ast_link = a_unary_op 

924 rv = ast.Unary_Expression( 

925 mh = self.mh, 

926 location = t_not.location, 

927 typ = self.builtin_bool, 

928 operator = a_unary_op, 

929 n_operand = rv) 

930 

931 return rv 

932 

933 else: 

934 return n_lhs 

935 

936 def parse_simple_expression(self, scope): 

937 # lobster-trace: LRM.Simple_Expression 

938 # lobster-trace: LRM.Operators 

939 # lobster-trace: LRM.Unary_Minus_Parsing 

940 assert isinstance(scope, ast.Scope) 

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

942 "-" : ast.Unary_Operator.MINUS} 

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

944 "-" : ast.Binary_Operator.MINUS} 

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

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

947 

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

949 self.nt.value in Parser.ADDING_OPERATOR: 

950 self.match("OPERATOR") 

951 t_unary = self.ct 

952 a_unary = un_add_map[t_unary.value] 

953 t_unary.ast_link = a_unary 

954 has_explicit_brackets = self.peek("BRA") 

955 else: 

956 t_unary = None 

957 

958 n_lhs = self.parse_term(scope) 

959 if t_unary: 

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

961 if self.lint_mode and \ 

962 isinstance(n_lhs, ast.Binary_Expression) and \ 

963 not has_explicit_brackets: 

964 self.mh.check(t_unary.location, 

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

966 "brackets to clarify intent" % 

967 n_lhs.to_string(), 

968 "unary_minus_precedence") 

969 

970 n_lhs = ast.Unary_Expression( 

971 mh = self.mh, 

972 location = t_unary.location, 

973 typ = n_lhs.typ, 

974 operator = a_unary, 

975 n_operand = n_lhs) 

976 

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

978 rtyp = self.builtin_str 

979 else: 

980 rtyp = n_lhs.typ 

981 

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

983 self.nt.value in Parser.ADDING_OPERATOR: 

984 self.match("OPERATOR") 

985 t_op = self.ct 

986 a_op = bin_add_map[t_op.value] 

987 t_op.ast_link = a_op 

988 n_rhs = self.parse_term(scope) 

989 n_lhs = ast.Binary_Expression( 

990 mh = self.mh, 

991 location = t_op.location, 

992 typ = rtyp, 

993 operator = a_op, 

994 n_lhs = n_lhs, 

995 n_rhs = n_rhs) 

996 

997 return n_lhs 

998 

999 def parse_term(self, scope): 

1000 # lobster-trace: LRM.Term 

1001 # lobster-trace: LRM.Operators 

1002 assert isinstance(scope, ast.Scope) 

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

1004 "/" : ast.Binary_Operator.DIVIDE, 

1005 "%" : ast.Binary_Operator.REMAINDER} 

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

1007 

1008 n_lhs = self.parse_factor(scope) 

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

1010 self.nt.value in Parser.MULTIPLYING_OPERATOR: 

1011 self.match("OPERATOR") 

1012 t_op = self.ct 

1013 a_op = mul_map[t_op.value] 

1014 t_op.ast_link = a_op 

1015 n_rhs = self.parse_factor(scope) 

1016 n_lhs = ast.Binary_Expression( 

1017 mh = self.mh, 

1018 location = t_op.location, 

1019 typ = n_lhs.typ, 

1020 operator = a_op, 

1021 n_lhs = n_lhs, 

1022 n_rhs = n_rhs) 

1023 

1024 return n_lhs 

1025 

1026 def parse_factor(self, scope): 

1027 # lobster-trace: LRM.Factor 

1028 assert isinstance(scope, ast.Scope) 

1029 

1030 if self.peek_kw("not"): 

1031 self.match_kw("not") 

1032 t_op = self.ct 

1033 n_operand = self.parse_primary(scope) 

1034 a_not = ast.Unary_Operator.LOGICAL_NOT 

1035 t_op.ast_link = a_not 

1036 return ast.Unary_Expression( 

1037 mh = self.mh, 

1038 location = t_op.location, 

1039 typ = self.builtin_bool, 

1040 operator = a_not, 

1041 n_operand = n_operand) 

1042 

1043 elif self.peek_kw("abs"): 

1044 self.match_kw("abs") 

1045 t_op = self.ct 

1046 n_operand = self.parse_primary(scope) 

1047 a_abs = ast.Unary_Operator.ABSOLUTE_VALUE 

1048 t_op.ast_link = a_abs 

1049 return ast.Unary_Expression( 

1050 mh = self.mh, 

1051 location = t_op.location, 

1052 typ = n_operand.typ, 

1053 operator = a_abs, 

1054 n_operand = n_operand) 

1055 

1056 else: 

1057 n_lhs = self.parse_primary(scope) 

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

1059 self.match("OPERATOR") 

1060 t_op = self.ct 

1061 n_rhs = self.parse_primary(scope) 

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

1063 a_binary = ast.Binary_Operator.POWER 

1064 t_op.ast_link = a_binary 

1065 n_lhs = ast.Binary_Expression( 

1066 mh = self.mh, 

1067 location = t_op.location, 

1068 typ = n_lhs.typ, 

1069 operator = a_binary, 

1070 n_lhs = n_lhs, 

1071 n_rhs = n_rhs) 

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

1073 self.mh.error(n_rhs.location, 

1074 "exponent must not be negative") 

1075 return n_lhs 

1076 

1077 def parse_primary(self, scope): 

1078 # lobster-trace: LRM.Primary 

1079 assert isinstance(scope, ast.Scope) 

1080 

1081 if self.peek("INTEGER"): 

1082 # lobster-trace: LRM.Integer_Values 

1083 self.match("INTEGER") 

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

1085 int_lit.set_ast_link(self.ct) 

1086 return int_lit 

1087 

1088 elif self.peek("DECIMAL"): 

1089 # lobster-trace: LRM.Decimal_Values 

1090 self.match("DECIMAL") 

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

1092 dec_lit.set_ast_link(self.ct) 

1093 return dec_lit 

1094 

1095 elif self.peek("STRING"): 

1096 # lobster-trace: LRM.String_Values 

1097 self.match("STRING") 

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

1099 string_lit.set_ast_link(self.ct) 

1100 return string_lit 

1101 

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

1103 # lobster-trace: LRM.Boolean_Values 

1104 self.match("KEYWORD") 

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

1106 bool_lit.set_ast_link(self.ct) 

1107 return bool_lit 

1108 

1109 elif self.peek_kw("null"): 

1110 self.match_kw("null") 

1111 null_lit = ast.Null_Literal(self.ct) 

1112 null_lit.set_ast_link(self.ct) 

1113 return null_lit 

1114 

1115 elif self.peek("BRA"): 

1116 self.match("BRA") 

1117 t_bra = self.ct 

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

1119 rv = self.parse_quantified_expression(scope) 

1120 elif self.peek_kw("if"): 

1121 rv = self.parse_conditional_expression(scope) 

1122 else: 

1123 rv = self.parse_expression(scope) 

1124 rv.set_ast_link(t_bra) 

1125 self.match("KET") 

1126 rv.set_ast_link(self.ct) 

1127 return rv 

1128 

1129 else: 

1130 return self.parse_name(scope) 

1131 

1132 def parse_quantified_expression(self, scope): 

1133 # lobster-trace: LRM.Quantified_Expression 

1134 assert isinstance(scope, ast.Scope) 

1135 

1136 if self.peek_kw("forall"): 

1137 self.match_kw("forall") 

1138 t_quantified = self.ct 

1139 universal = True 

1140 else: 

1141 self.match_kw("exists") 

1142 t_quantified = self.ct 

1143 universal = False 

1144 loc = self.ct.location 

1145 self.match("IDENTIFIER") 

1146 t_qv = self.ct 

1147 if scope.contains(t_qv.value): 

1148 # lobster-trace: LRM.Quantification_Naming_Scope 

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

1150 self.mh.error(t_qv.location, 

1151 "shadows %s %s from %s" % 

1152 (pdef.__class__.__name__, 

1153 pdef.name, 

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

1155 self.match_kw("in") 

1156 t_in = self.ct 

1157 self.match("IDENTIFIER") 

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

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

1160 field) 

1161 n_source.set_ast_link(self.ct) 

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

1163 # lobster-trace: LRM.Quantification_Object 

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

1165 "you can only quantify over arrays") 

1166 n_var = ast.Quantified_Variable(t_qv.value, 

1167 t_qv.location, 

1168 field.n_typ.element_type) 

1169 n_var.set_ast_link(t_qv) 

1170 self.match("ARROW") 

1171 t_arrow = self.ct 

1172 

1173 new_table = ast.Symbol_Table() 

1174 new_table.register(self.mh, n_var) 

1175 scope.push(new_table) 

1176 n_expr = self.parse_expression(scope) 

1177 scope.pop() 

1178 

1179 quantified_expression = ast.Quantified_Expression( 

1180 mh = self.mh, 

1181 location = loc, 

1182 typ = self.builtin_bool, 

1183 universal = universal, 

1184 n_variable = n_var, 

1185 n_source = n_source, 

1186 n_expr = n_expr) 

1187 

1188 quantified_expression.set_ast_link(t_quantified) 

1189 quantified_expression.set_ast_link(t_in) 

1190 quantified_expression.set_ast_link(t_arrow) 

1191 

1192 return quantified_expression 

1193 

1194 def parse_conditional_expression(self, scope): 

1195 # lobster-trace: LRM.Conditional_Expression 

1196 # lobster-trace: LRM.Restricted_Null 

1197 assert isinstance(scope, ast.Scope) 

1198 

1199 self.match_kw("if") 

1200 t_if = self.ct 

1201 if_cond = self.parse_expression(scope) 

1202 self.match_kw("then") 

1203 t_then = self.ct 

1204 if_expr = self.parse_expression(scope) 

1205 if if_expr.typ is None: 

1206 self.mh.error(if_expr.location, 

1207 "null is not permitted here") 

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

1209 

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

1211 if_action = if_action) 

1212 if_action.set_ast_link(t_if) 

1213 if_action.set_ast_link(t_then) 

1214 

1215 while self.peek_kw("elsif"): 

1216 self.match_kw("elsif") 

1217 t_elsif = self.ct 

1218 elsif_cond = self.parse_expression(scope) 

1219 self.match_kw("then") 

1220 t_then = self.ct 

1221 elsif_expr = self.parse_expression(scope) 

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

1223 elsif_action.set_ast_link(t_elsif) 

1224 elsif_action.set_ast_link(t_then) 

1225 rv.add_elsif(self.mh, elsif_action) 

1226 

1227 self.match_kw("else") 

1228 rv.set_ast_link(self.ct) 

1229 else_expr = self.parse_expression(scope) 

1230 rv.set_else_part(self.mh, else_expr) 

1231 

1232 return rv 

1233 

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

1235 # lobster-trace: LRM.Builtin_Functions 

1236 # lobster-trace: LRM.Builtin_Type_Conversion_Functions 

1237 assert isinstance(scope, ast.Scope) 

1238 assert isinstance(n_name, (ast.Builtin_Function, 

1239 ast.Builtin_Numeric_Type)) 

1240 assert isinstance(t_name, Token) 

1241 

1242 # Parse the arguments. 

1243 parameters = [] 

1244 n_name.set_ast_link(self.ct) 

1245 self.match("BRA") 

1246 n_name.set_ast_link(self.ct) 

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

1248 exp = self.parse_expression(scope) 

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

1250 exp.set_ast_link(self.ct) 

1251 parameters.append(exp) 

1252 

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

1254 self.match("COMMA") 

1255 n_name.set_ast_link(self.ct) 

1256 else: 

1257 break 

1258 self.match("KET") 

1259 n_name.set_ast_link(self.ct) 

1260 

1261 # Enforce arity 

1262 if isinstance(n_name, ast.Builtin_Function): 

1263 required_arity = n_name.arity 

1264 precise = not n_name.arity_at_least 

1265 else: 

1266 required_arity = 1 

1267 precise = True 

1268 

1269 if precise: 

1270 if required_arity != len(parameters): 

1271 self.mh.error(t_name.location, 

1272 "function requires %u parameters" % 

1273 n_name.arity) 

1274 else: 

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

1276 self.mh.error(t_name.location, 

1277 "function requires at least %u parameters" % 

1278 n_name.arity) 

1279 

1280 # Enforce types 

1281 if n_name.name == "len": 

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

1283 return ast.Unary_Expression( 

1284 mh = self.mh, 

1285 location = t_name.location, 

1286 typ = self.builtin_int, 

1287 operator = ast.Unary_Operator.STRING_LENGTH, 

1288 n_operand = parameters[0]) 

1289 else: 

1290 return ast.Unary_Expression( 

1291 mh = self.mh, 

1292 location = t_name.location, 

1293 typ = self.builtin_int, 

1294 operator = ast.Unary_Operator.ARRAY_LENGTH, 

1295 n_operand = parameters[0]) 

1296 

1297 elif n_name.name in ("startswith", 

1298 "endswith"): 

1299 return ast.Binary_Expression( 

1300 mh = self.mh, 

1301 location = t_name.location, 

1302 typ = self.builtin_bool, 

1303 operator = (ast.Binary_Operator.STRING_STARTSWITH 

1304 if "startswith" in n_name.name 

1305 else ast.Binary_Operator.STRING_ENDSWITH), 

1306 n_lhs = parameters[0], 

1307 n_rhs = parameters[1]) 

1308 

1309 elif n_name.name == "matches": 

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

1311 try: 

1312 # lobster-trace: LRM.Static_Regular_Expression 

1313 # scope is None on purpose to enforce static context 

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

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

1316 re.compile(value.value) 

1317 except re.error as err: 

1318 self.mh.error(value.location, 

1319 str(err)) 

1320 return ast.Binary_Expression( 

1321 mh = self.mh, 

1322 location = t_name.location, 

1323 typ = self.builtin_bool, 

1324 operator = ast.Binary_Operator.STRING_REGEX, 

1325 n_lhs = parameters[0], 

1326 n_rhs = parameters[1]) 

1327 

1328 elif n_name.name == "oneof": 

1329 return ast.OneOf_Expression( 

1330 mh = self.mh, 

1331 location = t_name.location, 

1332 typ = self.builtin_bool, 

1333 choices = parameters) 

1334 

1335 elif isinstance(n_name, ast.Builtin_Numeric_Type): 

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

1337 if isinstance(n_name, ast.Builtin_Integer): 

1338 return ast.Unary_Expression( 

1339 mh = self.mh, 

1340 location = t_name.location, 

1341 typ = self.builtin_int, 

1342 operator = ast.Unary_Operator.CONVERSION_TO_INT, 

1343 n_operand = parameters[0]) 

1344 elif isinstance(n_name, ast.Builtin_Decimal): 

1345 return ast.Unary_Expression( 

1346 mh = self.mh, 

1347 location = t_name.location, 

1348 typ = self.builtin_decimal, 

1349 operator = ast.Unary_Operator.CONVERSION_TO_DECIMAL, 

1350 n_operand = parameters[0]) 

1351 else: 

1352 self.mh.ice_loc(t_name.location, 

1353 "unexpected type conversion") 

1354 

1355 else: 

1356 self.mh.ice_loc(t_name.location, 

1357 "unexpected builtin") 

1358 

1359 def parse_name(self, scope): 

1360 # lobster-trace: LRM.Names 

1361 

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

1363 # 

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

1365 # 

1366 # name ::= qualified_name 

1367 # | name '.' IDENTIFIER 

1368 # | name '[' expression ']' 

1369 # | name '(' parameter_list ')' 

1370 # 

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

1372 

1373 assert isinstance(scope, ast.Scope) 

1374 

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

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

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

1378 # components. However as functions cannot be stored in 

1379 # components the true grammar for function calls is always 

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

1381 

1382 # lobster-trace: LRM.Builtin_Functions 

1383 # lobster-trace: LRM.Builtin_Type_Conversion_Functions 

1384 self.match("IDENTIFIER") 

1385 if self.peek("BRA"): 

1386 # If we follow our name with brackets 

1387 # immediately, we have a builtin function call. 

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

1389 self.ct) 

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

1391 ast.Builtin_Numeric_Type)): 

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

1393 "not a valid builtin function " 

1394 "or numeric type") 

1395 else: 

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

1397 

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

1399 # first. 

1400 if isinstance(n_name, ast.Enumeration_Type): 

1401 n_name.set_ast_link(self.ct) 

1402 self.match("DOT") 

1403 n_name.set_ast_link(self.ct) 

1404 self.match("IDENTIFIER") 

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

1406 self.ct, 

1407 ast.Enumeration_Literal_Spec) 

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

1409 literal = lit) 

1410 enum_lit.set_ast_link(self.ct) 

1411 return enum_lit 

1412 

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

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

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

1416 ast.Builtin_Numeric_Type, 

1417 ast.Composite_Component, 

1418 ast.Quantified_Variable, 

1419 )): 

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

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

1422 (n_name.__class__.__name__, 

1423 n_name.name)) 

1424 

1425 # Right now function calls and type conversions must be 

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

1427 if isinstance(n_name, (ast.Builtin_Function, 

1428 ast.Builtin_Numeric_Type)): 

1429 # lobster-trace: LRM.Builtin_Functions 

1430 # lobster-trace: LRM.Builtin_Type_Conversion_Functions 

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

1432 

1433 assert isinstance(n_name, (ast.Composite_Component, 

1434 ast.Quantified_Variable)) 

1435 

1436 # We now process the potentially recursive part: 

1437 # | name '.' IDENTIFIER 

1438 # | name '[' expression ']' 

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

1440 entity = n_name) 

1441 n_name.set_ast_link(self.ct) 

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

1443 if self.peek("DOT"): 

1444 if not isinstance(n_name.typ, (ast.Tuple_Type, 1444 ↛ 1448line 1444 didn't jump to line 1448 because the condition on line 1444 was never true

1445 ast.Record_Type, 

1446 ast.Union_Type)): 

1447 # lobster-trace: LRM.Valid_Index_Prefixes 

1448 self.mh.error(n_name.location, 

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

1450 "which is not a tuple, record, or union" % 

1451 (n_name.to_string(), 

1452 n_name.typ.name)) 

1453 

1454 self.match("DOT") 

1455 t_dot = self.ct 

1456 self.match("IDENTIFIER") 

1457 t_field = self.ct 

1458 

1459 is_union_access = isinstance(n_name.typ, 

1460 ast.Union_Type) 

1461 is_universal = True 

1462 

1463 if is_union_access: 

1464 # lobster-trace: LRM.Union_Type_Field_Access 

1465 # lobster-trace: LRM.Union_Type_Field_Type_Conflict 

1466 # lobster-trace: LRM.Union_Type_Field_Access_Validity 

1467 field_name = t_field.value 

1468 field_map = n_name.typ.get_field_map() 

1469 if field_name not in field_map: 

1470 self.mh.error( 

1471 t_field.location, 

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

1473 " of union type %s" % 

1474 (field_name, n_name.typ.name)) 

1475 info = field_map[field_name] 

1476 if info["n_typ"] is None: 

1477 self.mh.error( 

1478 t_field.location, 

1479 "field %s has conflicting types in" 

1480 " members of union type %s" % 

1481 (field_name, n_name.typ.name)) 

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

1483 # lobster-trace: LRM.Union_Type_Partial_Field_Access 

1484 if self.lint_mode and not is_universal: 

1485 self.mh.check( 

1486 t_field.location, 

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

1488 " members of union type %s;" 

1489 " accessing it on other members" 

1490 " returns null" % 

1491 (field_name, 

1492 info["count"], 

1493 info["total"], 

1494 n_name.typ.name), 

1495 "union_partial_field_access") 

1496 n_field = info["component"] 

1497 else: 

1498 n_field = n_name.typ.components.lookup( 

1499 self.mh, 

1500 t_field, 

1501 ast.Composite_Component) 

1502 

1503 n_field.set_ast_link(t_field) 

1504 n_name = ast.Field_Access_Expression( 

1505 mh = self.mh, 

1506 location = t_field.location, 

1507 n_prefix = n_name, 

1508 n_field = n_field, 

1509 is_union_access = is_union_access, 

1510 is_universal = is_universal) 

1511 n_name.set_ast_link(t_dot) 

1512 

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

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

1515 self.mh.error(n_name.location, 

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

1517 "which is not an array" % 

1518 (n_name.to_string(), 

1519 n_name.typ.name)) 

1520 

1521 self.match("S_BRA") 

1522 t_bracket = self.ct 

1523 n_index = self.parse_expression(scope) 

1524 self.match("S_KET") 

1525 a_binary = ast.Binary_Operator.INDEX 

1526 t_bracket.ast_link = a_binary 

1527 self.ct.ast_link = a_binary 

1528 

1529 n_name = ast.Binary_Expression( 

1530 mh = self.mh, 

1531 location = t_bracket.location, 

1532 typ = n_name.typ.element_type, 

1533 operator = a_binary, 

1534 n_lhs = n_name, 

1535 n_rhs = n_index) 

1536 

1537 return n_name 

1538 

1539 def parse_check_block(self): 

1540 # lobster-trace: LRM.Check_Block 

1541 t_severity = None 

1542 self.match_kw("checks") 

1543 t_checks = self.ct 

1544 self.match("IDENTIFIER") 

1545 # lobster-trace: LRM.Applicable_Types 

1546 # lobster-trace: LRM.Applicable_Components 

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

1548 self.ct, 

1549 ast.Composite_Type) 

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

1551 n_typ = n_ctype) 

1552 n_check_block.set_ast_link(t_checks) 

1553 n_ctype.set_ast_link(self.ct) 

1554 scope = ast.Scope() 

1555 scope.push(self.stab) 

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

1557 scope.push(n_ctype.components) 

1558 self.match("C_BRA") 

1559 n_check_block.set_ast_link(self.ct) 

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

1561 c_expr = self.parse_expression(scope) 

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

1563 self.mh.error(c_expr.location, 

1564 "check expression must be Boolean") 

1565 

1566 self.match("COMMA") 

1567 t_first_comma = self.ct 

1568 if self.peek("KEYWORD"): 

1569 self.match("KEYWORD") 

1570 t_severity = self.ct 

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

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

1573 "expected warning|error|fatal") 

1574 c_sev = self.ct.value 

1575 else: 

1576 c_sev = "error" 

1577 

1578 self.match("STRING") 

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

1580 # lobster-trace: LRM.No_Newlines_In_Message 

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

1582 "error message must not contain a newline", 

1583 fatal = False) 

1584 t_msg = self.ct 

1585 

1586 has_extrainfo = False 

1587 has_anchor = False 

1588 if self.peek("COMMA"): 

1589 self.match("COMMA") 

1590 t_second_comma = self.ct 

1591 if self.peek("IDENTIFIER"): 

1592 has_anchor = True 

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

1594 has_extrainfo = True 

1595 else: 

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

1597 "expected either a details string or" 

1598 " identifier to anchor the check message") 

1599 

1600 if has_extrainfo: 

1601 self.match("STRING") 

1602 t_extrainfo = self.ct 

1603 c_extrainfo = self.ct.value 

1604 

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

1606 self.match("COMMA") 

1607 t_third_comma = self.ct 

1608 has_anchor = True 

1609 

1610 else: 

1611 c_extrainfo = None 

1612 

1613 if has_anchor: 

1614 self.match("IDENTIFIER") 

1615 t_anchor = self.ct 

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

1617 self.ct, 

1618 ast.Composite_Component) 

1619 else: 

1620 c_anchor = None 

1621 

1622 n_check = ast.Check(n_type = n_ctype, 

1623 n_expr = c_expr, 

1624 n_anchor = c_anchor, 

1625 severity = c_sev, 

1626 t_message = t_msg, 

1627 extrainfo = c_extrainfo) 

1628 

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

1630 # pylint: disable=used-before-assignment 

1631 

1632 n_check.set_ast_link(t_first_comma) 

1633 if t_severity: 

1634 n_check.set_ast_link(t_severity) 

1635 n_check.set_ast_link(t_msg) 

1636 if c_extrainfo or c_anchor: 

1637 n_check.set_ast_link(t_second_comma) 

1638 if c_extrainfo: 

1639 n_check.set_ast_link(t_extrainfo) 

1640 if c_anchor: 

1641 c_anchor.set_ast_link(t_anchor) 

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

1643 n_check.set_ast_link(t_third_comma) 

1644 

1645 n_ctype.add_check(n_check) 

1646 n_check_block.add_check(n_check) 

1647 

1648 assert scope.size() == 3 

1649 

1650 self.match("C_KET") 

1651 n_check_block.set_ast_link(self.ct) 

1652 

1653 return n_check_block 

1654 

1655 def parse_section_declaration(self): 

1656 # lobster-trace: LRM.Section_Declaration 

1657 self.match_kw("section") 

1658 t_section = self.ct 

1659 self.match("STRING") 

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

1661 location = self.ct.location, 

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

1663 sec.set_ast_link(self.ct) 

1664 sec.set_ast_link(t_section) 

1665 self.section.append(sec) 

1666 self.match("C_BRA") 

1667 sec.set_ast_link(self.ct) 

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

1669 self.parse_trlc_entry() 

1670 self.match("C_KET") 

1671 sec.set_ast_link(self.ct) 

1672 self.section.pop() 

1673 

1674 def parse_boolean(self): 

1675 # lobster-trace: LRM.Boolean_Values 

1676 self.match("KEYWORD") 

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

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

1679 else: 

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

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

1682 

1683 def parse_value(self, typ): 

1684 # lobster-trace: LRM.Tuple_Syntax_Correct_Form 

1685 assert isinstance(typ, ast.Type) 

1686 

1687 if isinstance(typ, ast.Builtin_Numeric_Type): 

1688 # lobster-trace: LRM.Integer_Values 

1689 # lobster-trace: LRM.Decimal_Values 

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

1691 self.nt.value in Parser.ADDING_OPERATOR: 

1692 self.match("OPERATOR") 

1693 t_op = self.ct 

1694 e_op = (ast.Unary_Operator.PLUS 

1695 if t_op.value == "+" 

1696 else ast.Unary_Operator.MINUS) 

1697 t_op.ast_link = e_op 

1698 else: 

1699 t_op = None 

1700 

1701 if isinstance(typ, ast.Builtin_Decimal): 

1702 self.match("DECIMAL") 

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

1704 rv.set_ast_link(self.ct) 

1705 elif isinstance(typ, ast.Builtin_Integer): 

1706 self.match("INTEGER") 

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

1708 rv.set_ast_link(self.ct) 

1709 else: 

1710 assert False 

1711 

1712 if t_op: 

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

1714 location = t_op.location, 

1715 typ = rv.typ, 

1716 operator = e_op, 

1717 n_operand = rv) 

1718 

1719 return rv 

1720 

1721 elif isinstance(typ, ast.Builtin_Markup_String): 

1722 # lobster-trace: LRM.Markup_String_Values 

1723 return self.parse_markup_string() 

1724 

1725 elif isinstance(typ, ast.Builtin_String): 

1726 # lobster-trace: LRM.String_Values 

1727 self.match("STRING") 

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

1729 rv.set_ast_link(self.ct) 

1730 return rv 

1731 

1732 elif isinstance(typ, ast.Builtin_Boolean): 

1733 rv = self.parse_boolean() 

1734 rv.set_ast_link(self.ct) 

1735 return rv 

1736 

1737 elif isinstance(typ, ast.Array_Type): 

1738 self.match("S_BRA") 

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

1740 typ) 

1741 rv.set_ast_link(self.ct) 

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

1743 array_elem = self.parse_value(typ.element_type) 

1744 rv.append(array_elem) 

1745 if self.peek("COMMA"): 

1746 self.match("COMMA") 

1747 rv.set_ast_link(self.ct) 

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

1749 break 

1750 else: 

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

1752 "comma separating array elements is " 

1753 "missing", 

1754 fatal = False) 

1755 

1756 self.match("S_KET") 

1757 rv.set_ast_link(self.ct) 

1758 

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

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

1761 "this array requires at least %u elements " 

1762 "(only %u provided)" % 

1763 (typ.lower_bound, 

1764 len(rv.value)), 

1765 fatal=False) 

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

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

1768 "this array requires at most %u elements " 

1769 "(%u provided)" % 

1770 (typ.upper_bound, 

1771 len(rv.value)), 

1772 fatal=False) 

1773 

1774 return rv 

1775 

1776 elif isinstance(typ, ast.Enumeration_Type): 

1777 enum = self.parse_qualified_name(self.default_scope, 

1778 ast.Enumeration_Type) 

1779 enum.set_ast_link(self.ct) 

1780 if enum != typ: 

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

1782 "expected %s" % typ.name) 

1783 self.match("DOT") 

1784 enum.set_ast_link(self.ct) 

1785 self.match("IDENTIFIER") 

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

1787 self.ct, 

1788 ast.Enumeration_Literal_Spec) 

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

1790 lit) 

1791 

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

1793 self.match("IDENTIFIER") 

1794 t_name = self.ct 

1795 if self.peek("DOT"): 

1796 self.match("DOT") 

1797 t_dot = self.ct 

1798 self.match("IDENTIFIER") 

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

1800 the_pkg.set_ast_link(t_name) 

1801 the_pkg.set_ast_link(t_dot) 

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

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

1804 "package must be imported before use") 

1805 t_name = self.ct 

1806 else: 

1807 the_pkg = self.cu.package 

1808 

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

1810 name = t_name.value, 

1811 typ = typ, 

1812 package = the_pkg) 

1813 rv.set_ast_link(t_name) 

1814 

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

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

1817 rv.resolve_references(self.mh) 

1818 

1819 return rv 

1820 

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

1822 # lobster-trace: LRM.Tuple_Separator_Form 

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

1824 

1825 next_is_optional = False 

1826 for n_item in typ.iter_sequence(): 

1827 if isinstance(n_item, ast.Composite_Component): 

1828 if next_is_optional and n_item.optional: 

1829 break 

1830 value = self.parse_value(n_item.n_typ) 

1831 rv.assign(n_item.name, value) 

1832 

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

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

1835 self.match(n_item.token.kind) 

1836 n_item.set_ast_link(self.ct) 

1837 else: 

1838 next_is_optional = True 

1839 

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

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

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

1843 self.match("IDENTIFIER") 

1844 n_item.set_ast_link(self.ct) 

1845 else: 

1846 next_is_optional = True 

1847 

1848 else: 

1849 assert False 

1850 

1851 return rv 

1852 

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

1854 # lobster-trace: LRM.Tuple_Generic_Form 

1855 self.match("BRA") 

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

1857 rv.set_ast_link(self.ct) 

1858 

1859 first = True 

1860 for n_field in typ.iter_sequence(): 

1861 if first: 

1862 first = False 

1863 else: 

1864 self.match("COMMA") 

1865 rv.set_ast_link(self.ct) 

1866 rv.assign(n_field.name, 

1867 self.parse_value(n_field.n_typ)) 

1868 

1869 self.match("KET") 

1870 rv.set_ast_link(self.ct) 

1871 return rv 

1872 

1873 else: 

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

1875 "logic error: unexpected type %s" % 

1876 typ.__class__.__name__) 

1877 

1878 def parse_markup_string(self): 

1879 # lobster-trace: LRM.Markup_String_Values 

1880 self.match("STRING") 

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

1882 mpar = Markup_Parser(self, rv) 

1883 mpar.parse_all_references() 

1884 return rv 

1885 

1886 def parse_record_object_declaration(self): 

1887 # lobster-trace: LRM.Section_Declaration 

1888 # lobster-trace: LRM.Record_Object_Declaration 

1889 # lobster-trace: LRM.Valid_Record_Types 

1890 # lobster-trace: LRM.Valid_Components 

1891 # lobster-trace: LRM.Valid_Enumeration_Literals 

1892 # lobster-trace: LRM.Mandatory_Components 

1893 # lobster-trace: LRM.Evaluation_Of_Checks 

1894 # lobster-trace: LRM.Single_Value_Assignment 

1895 

1896 r_typ = self.parse_qualified_name(self.default_scope, 

1897 ast.Record_Type) 

1898 r_typ.set_ast_link(self.ct) 

1899 # lobster-trace: LRM.Abstract_Types 

1900 if r_typ.is_abstract: 

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

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

1903 r_typ.name) 

1904 

1905 self.match("IDENTIFIER") 

1906 obj = ast.Record_Object( 

1907 name = self.ct.value, 

1908 location = self.ct.location, 

1909 n_typ = r_typ, 

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

1911 n_package = self.cu.package) 

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

1913 obj.set_ast_link(self.ct) 

1914 

1915 self.match("C_BRA") 

1916 obj.set_ast_link(self.ct) 

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

1918 self.match("IDENTIFIER") 

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

1920 self.ct, 

1921 ast.Composite_Component) 

1922 if obj.is_component_implicit_null(comp): 

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

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

1925 (comp.name, 

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

1927 comp.set_ast_link(self.ct) 

1928 if r_typ.is_frozen(comp): 

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

1930 "cannot overwrite frozen component %s" % 

1931 comp.name) 

1932 self.match("ASSIGN") 

1933 comp.set_ast_link(self.ct) 

1934 value = self.parse_value(comp.n_typ) 

1935 if not self.ct.ast_link: 

1936 value.set_ast_link(self.ct) 

1937 obj.assign(comp, value) 

1938 

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

1940 for comp in r_typ.all_components(): 

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

1942 if r_typ.is_frozen(comp): 

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

1944 elif not comp.optional: 

1945 self.mh.error( 

1946 obj.location, 

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

1948 (comp.name, 

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

1950 

1951 self.match("C_KET") 

1952 obj.set_ast_link(self.ct) 

1953 

1954 return obj 

1955 

1956 def parse_trlc_entry(self): 

1957 # lobster-trace: LRM.TRLC_File 

1958 if self.peek_kw("section"): 

1959 self.parse_section_declaration() 

1960 else: 

1961 self.cu.add_item(self.parse_record_object_declaration()) 

1962 

1963 def parse_preamble(self, kind): 

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

1965 # lobster-trace: LRM.Layout 

1966 # lobster-trace: LRM.Preamble 

1967 

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

1969 # needed 

1970 self.match_kw("package") 

1971 t_pkg = self.ct 

1972 self.match("IDENTIFIER") 

1973 

1974 if kind == "rsl": 

1975 declare_package = True 

1976 else: 

1977 # lobster-trace: LRM.Late_Package_Declarations 

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

1979 

1980 if declare_package: 

1981 # lobster-trace: LRM.Package_Declaration 

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

1983 location = self.ct.location, 

1984 builtin_stab = self.stab, 

1985 declared_late = kind == "trlc") 

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

1987 else: 

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

1989 

1990 pkg.set_ast_link(t_pkg) 

1991 pkg.set_ast_link(self.ct) 

1992 

1993 # lobster-trace: LRM.Current_Package 

1994 self.cu.set_package(pkg) 

1995 

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

1997 

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

1999 # lobster-trace: LRM.Import_Visibility 

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

2001 while self.peek_kw("import"): 

2002 self.match_kw("import") 

2003 pkg.set_ast_link(self.ct) 

2004 self.match("IDENTIFIER") 

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

2006 

2007 def parse_rsl_file(self): 

2008 # lobster-trace: LRM.RSL_File 

2009 assert self.cu.package is not None 

2010 

2011 ok = True 

2012 while not self.peek_eof(): 

2013 try: 

2014 if self.peek_kw("checks"): 

2015 self.cu.add_item(self.parse_check_block()) 

2016 else: 

2017 self.cu.add_item(self.parse_type_declaration()) 

2018 except TRLC_Error as err: 

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

2020 raise 

2021 

2022 ok = False 

2023 

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

2025 # relevant keyword 

2026 self.skip_until_newline() 

2027 while not self.peek_eof(): 

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

2029 self.peek_kw("type") or \ 

2030 self.peek_kw("abstract") or \ 

2031 self.peek_kw("final") or \ 

2032 self.peek_kw("tuple") or \ 

2033 self.peek_kw("enum"): 

2034 break 

2035 self.advance() 

2036 self.skip_until_newline() 

2037 

2038 self.match_eof() 

2039 

2040 for tok in self.lexer.tokens: 

2041 if tok.kind == "COMMENT": 

2042 self.cu.package.set_ast_link(tok) 

2043 

2044 return ok 

2045 

2046 def parse_trlc_file(self): 

2047 # lobster-trace: LRM.TRLC_File 

2048 assert self.cu.package is not None 

2049 

2050 ok = True 

2051 

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

2053 try: 

2054 self.parse_trlc_entry() 

2055 except TRLC_Error as err: 

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

2057 raise 

2058 

2059 ok = False 

2060 

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

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

2063 # EOF 

2064 self.skip_until_newline() 

2065 while not self.peek_eof(): 

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

2067 break 

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

2069 pass 

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

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

2072 self.nt.value) 

2073 if isinstance(n_sym, ast.Package): 

2074 break 

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

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

2077 self.mh, 

2078 self.nt.value) 

2079 if isinstance(n_sym, ast.Record_Type): 

2080 break 

2081 self.advance() 

2082 self.skip_until_newline() 

2083 

2084 self.match_eof() 

2085 

2086 for tok in self.lexer.tokens: 

2087 if tok.kind == "COMMENT": 

2088 self.cu.package.set_ast_link(tok) 

2089 

2090 return ok