Coverage for trlc/ast.py: 90%

1289 statements  

« prev     ^ index     » next       coverage.py v7.10.7, created at 2026-04-14 14:54 +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) 2024-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 

22from abc import ABCMeta, abstractmethod 

23import re 

24 

25from copy import copy 

26from difflib import get_close_matches 

27from enum import Enum, auto 

28from collections import OrderedDict 

29from fractions import Fraction 

30 

31from trlc.errors import TRLC_Error, Location, Message_Handler 

32from trlc.lexer import Token 

33from trlc import math 

34 

35# 

36# This module defines the AST and related object for the TRLC 

37# reference implementation. There are four sections: 

38# 

39# - Valuations deal with concrete values for record objects 

40# - AST expressions deal with the syntax tree 

41# - AST entities deal with concrete objects that have been declared 

42# - Symbol_Table and scope deals with name resolution 

43# 

44 

45 

46############################################################################## 

47# Valuations 

48############################################################################## 

49 

50class Value: 

51 # lobster-trace: LRM.Boolean_Values 

52 # lobster-trace: LRM.Integer_Values 

53 # lobster-trace: LRM.Decimal_Values 

54 # lobster-trace: LRM.String_Values 

55 # lobster-trace: LRM.Markup_String_Values 

56 """Polymorphic value for evaluating expressions. 

57 

58 Any record references will be fully resolved. 

59 

60 :attribute location: source location this value comes from 

61 :type: Location 

62 

63 :attribute value: the value or None (for null values) 

64 :type: str, int, bool, fractions.Fraction, list[Value], \ 

65 Record_Reference, Enumeration_Literal_Spec 

66 

67 :attribute typ: type of the value (or None for null values) 

68 :type: Type 

69 """ 

70 def __init__(self, location, value, typ): 

71 assert isinstance(location, Location) 

72 assert value is None or \ 

73 isinstance(value, (str, 

74 int, 

75 bool, 

76 list, # for arrays 

77 dict, # for tuples 

78 Fraction, 

79 Record_Reference, 

80 Enumeration_Literal_Spec)) 

81 assert typ is None or isinstance(typ, Type) 

82 assert (typ is None) == (value is None) 

83 

84 self.location = location 

85 self.value = value 

86 self.typ = typ 

87 

88 def __eq__(self, other): 

89 return self.typ == other.typ and self.value == other.value 

90 

91 def __repr__(self): # pragma: no cover 

92 return "Value(%s)" % self.value 

93 

94 def resolve_references(self, mh): 

95 assert isinstance(mh, Message_Handler) 

96 

97 if isinstance(self.value, Record_Reference): 

98 self.value.resolve(mh) 

99 

100 

101############################################################################## 

102# AST Nodes 

103############################################################################## 

104 

105class Node(metaclass=ABCMeta): 

106 """Base class for all AST items. 

107 

108 :attribute location: source location 

109 :type: Location 

110 """ 

111 def __init__(self, location): 

112 # lobster-exclude: Constructor only declares variables 

113 assert isinstance(location, Location) 

114 self.location = location 

115 

116 def set_ast_link(self, tok): 

117 assert isinstance(tok, Token) 

118 tok.ast_link = self 

119 

120 def write_indent(self, indent, message): # pragma: no cover 

121 # lobster-exclude: Debugging feature 

122 assert isinstance(indent, int) 

123 assert indent >= 0 

124 assert isinstance(message, str) 

125 print(" " * (3 * indent) + message) 

126 

127 @abstractmethod 

128 def dump(self, indent=0): # pragma: no cover 

129 """Visualise the parse tree. 

130 

131 This can be called for any :class:`Node` or 

132 :class:`Symbol_Table`, and can be very helpful for debugging 

133 or understanding the parse tree. The dump methods will produce 

134 output like this:: 

135 

136 Symbol_Table 

137 Builtin_Boolean 

138 Builtin_Integer 

139 Builtin_Decimal 

140 Builtin_String 

141 Builtin_Markup_String 

142 Package bar 

143 Symbol_Table 

144 Record_Type MyType 

145 Composite_Component name 

146 Optional: False 

147 Type: String 

148 Checks 

149 Error 'description is too short' 

150 Anchor: description 

151 Binary Binary_Operator.COMP_GT Expression 

152 Type: Boolean 

153 Unary Unary_Operator.STRING_LENGTH Expression 

154 Type: Integer 

155 Name Reference to description 

156 Integer Literal 10 

157 Package instances 

158 Symbol_Table 

159 Record_Object SomeThing 

160 Type: MyType 

161 Field description: "Potato" 

162 Builtin_Function endswith 

163 Builtin_Function len 

164 Builtin_Function matches 

165 Builtin_Function startswith 

166 Builtin_Function oneof 

167 

168 """ 

169 assert isinstance(indent, int) and indent >= 0 

170 assert False, f"dump not implemented for {self.__class__.__name__}" 

171 # lobster-exclude: Debugging feature 

172 

173 

174class Check_Block(Node): 

175 """Node representing check blocks 

176 

177 Semantically this has no meaning, but it's nice to have some kind 

178 of similar representation to how it's in the file. 

179 

180 :attribute n_typ: composite type for which the checks apply 

181 :type: Composite_Type 

182 

183 :attribute checks: list of checks 

184 :type: list[Check] 

185 

186 """ 

187 def __init__(self, location, n_typ): 

188 # lobster-trace: LRM.Check_Block 

189 super().__init__(location) 

190 assert isinstance(n_typ, Composite_Type) 

191 self.n_typ = n_typ 

192 self.checks = [] 

193 

194 def add_check(self, n_check): 

195 # lobster-trace: LRM.Check_Evaluation_Order 

196 assert isinstance(n_check, Check) 

197 self.checks.append(n_check) 

198 

199 def dump(self, indent=0): # pragma: no cover 

200 # lobster-exclude: Debugging feature 

201 self.write_indent(indent, "Check_Block") 

202 self.write_indent(indent + 1, f"Type: {self.n_typ.name}") 

203 for n_check in self.checks: 

204 n_check.dump(indent + 1) 

205 

206 

207class Compilation_Unit(Node): 

208 """Special node to represent the concrete file structure 

209 

210 :attribute package: the main package this file declares or contributes to 

211 :type: Package 

212 

213 :attribute imports: package imported by this file 

214 :type: list[Package] 

215 

216 :attribute items: list of 

217 :type: list[Node] 

218 

219 """ 

220 def __init__(self, file_name): 

221 # lobster-exclude: Constructor only declares variables 

222 super().__init__(Location(file_name)) 

223 self.package = None 

224 self.imports = None 

225 self.raw_imports = [] 

226 self.items = [] 

227 

228 def dump(self, indent=0): # pragma: no cover 

229 # lobster-exclude: Debugging feature 

230 self.write_indent(indent, f"Compilation_Unit ({self.location.file_name})") 

231 for t_import in self.raw_imports: 

232 self.write_indent(indent + 1, f"Import: {t_import.value}") 

233 for n_item in self.items: 

234 n_item.dump(indent + 1) 

235 

236 def set_package(self, pkg): 

237 # lobster-trace: LRM.Current_Package 

238 assert isinstance(pkg, Package) 

239 self.package = pkg 

240 

241 def add_import(self, mh, t_import): 

242 # lobster-trace: LRM.Import_Visibility 

243 # lobster-trace: LRM.Self_Imports 

244 assert isinstance(mh, Message_Handler) 

245 assert isinstance(t_import, Token) 

246 assert t_import.kind == "IDENTIFIER" 

247 

248 if t_import.value == self.package.name: 

249 mh.error(t_import.location, 

250 "package %s cannot import itself" % self.package.name) 

251 

252 # Skip duplicates 

253 for t_previous in self.raw_imports: 

254 if t_previous.value == t_import.value: 

255 mh.warning(t_import.location, 

256 "duplicate import of package %s" % t_import.value) 

257 return 

258 

259 self.raw_imports.append(t_import) 

260 

261 def resolve_imports(self, mh, stab): 

262 # lobster-trace: LRM.Import_Visibility 

263 assert isinstance(mh, Message_Handler) 

264 assert isinstance(stab, Symbol_Table) 

265 self.imports = set() 

266 for t_import in self.raw_imports: 

267 # We can ignore errors here, because that just means we 

268 # generate more error later. 

269 try: 

270 a_import = stab.lookup(mh, t_import, Package) 

271 self.imports.add(a_import) 

272 a_import.set_ast_link(t_import) 

273 except TRLC_Error: 

274 pass 

275 

276 def is_visible(self, n_pkg): 

277 # lobster-trace: LRM.Import_Visibility 

278 assert self.imports is not None 

279 assert isinstance(n_pkg, Package) 

280 return n_pkg == self.package or n_pkg in self.imports 

281 

282 def add_item(self, node): 

283 # lobster-trace: LRM.RSL_File 

284 # lobster-trace: LRM.TRLC_File 

285 assert isinstance(node, (Concrete_Type, 

286 Check_Block, 

287 Record_Object)), \ 

288 "trying to add %s to a compilation unit" % node.__class__.__name__ 

289 self.items.append(node) 

290 

291 

292class Check(Node): 

293 """User defined check 

294 

295 This represent a single user-defined check inside a check block:: 

296 

297 checks T { 

298 a /= null implies a > 5, warning "potato", a 

299 ^^^^^^^^^^^^^^^^^^^^^^^1 ^2 ^3 ^4 

300 

301 :attribute n_type: The tuple/record type this check applies to 

302 :type: Composite_Type 

303 

304 :attribute n_expr: The boolean expression for the check (see 1) 

305 :type: Expression 

306 

307 :attribute n_anchor: The (optional) record component where the message \ 

308 should be issued (or None) (see 4) 

309 :type: Composite_Component 

310 

311 :attribute severity: warning, error, or fatal (see 2; also if this is \ 

312 not specified the default is 'error') 

313 :type: str 

314 

315 :attribute message: the user-supplied message (see 3) 

316 :type: str 

317 """ 

318 def __init__(self, 

319 n_type, 

320 n_expr, 

321 n_anchor, 

322 severity, 

323 t_message, 

324 extrainfo): 

325 # lobster-trace: LRM.Check_Block 

326 assert isinstance(n_type, Composite_Type) 

327 assert isinstance(n_expr, Expression) 

328 assert isinstance(n_anchor, Composite_Component) or n_anchor is None 

329 assert severity in ("warning", "error", "fatal") 

330 assert isinstance(t_message, Token) 

331 assert t_message.kind == "STRING" 

332 assert isinstance(extrainfo, str) or extrainfo is None 

333 super().__init__(t_message.location) 

334 

335 self.n_type = n_type 

336 self.n_expr = n_expr 

337 self.n_anchor = n_anchor 

338 self.severity = severity 

339 # lobster-trace: LRM.No_Newlines_In_Message 

340 # This is the error recovery strategy if we find newlines in 

341 # the short error messages: we just remove them. The error 

342 # raised is non-fatal. 

343 self.message = t_message.value.replace("\n", " ") 

344 self.extrainfo = extrainfo 

345 

346 def dump(self, indent=0): # pragma: no cover 

347 # lobster-exclude: Debugging feature 

348 if self.severity == "warning": 

349 self.write_indent(indent, f"Warning '{self.message}'") 

350 elif self.severity == "error": 

351 self.write_indent(indent, f"Error '{self.message}'") 

352 else: 

353 self.write_indent(indent, f"Fatal error '{self.message}'") 

354 if self.n_anchor: 

355 self.write_indent(indent + 1, f"Anchor: {self.n_anchor.name}") 

356 self.n_expr.dump(indent + 1) 

357 

358 def get_real_location(self, composite_object): 

359 # lobster-exclude: LRM.Anchoring 

360 assert isinstance(composite_object, (Record_Object, 

361 Tuple_Aggregate)) 

362 if isinstance(composite_object, Record_Object): 

363 fields = composite_object.field 

364 else: 

365 fields = composite_object.value 

366 

367 if self.n_anchor is None or fields[self.n_anchor.name] is None: 

368 return composite_object.location 

369 else: 

370 return fields[self.n_anchor.name].location 

371 

372 def perform(self, mh, composite_object, gstab): 

373 # lobster-trace: LRM.Check_Messages 

374 # lobster-trace: LRM.Check_Severity 

375 assert isinstance(mh, Message_Handler) 

376 assert isinstance(composite_object, (Record_Object, 

377 Tuple_Aggregate)) 

378 assert isinstance(gstab, Symbol_Table) 

379 

380 if isinstance(composite_object, Record_Object): 

381 result = self.n_expr.evaluate(mh, 

382 copy(composite_object.field), 

383 gstab) 

384 else: 

385 result = self.n_expr.evaluate(mh, 

386 copy(composite_object.value), 

387 gstab) 

388 if result.value is None: 

389 loc = self.get_real_location(composite_object) 

390 mh.error(loc, 

391 "check %s (%s) evaluates to null" % 

392 (self.n_expr.to_string(), 

393 mh.cross_file_reference(self.location))) 

394 

395 assert isinstance(result.value, bool) 

396 

397 if not result.value: 

398 loc = self.get_real_location(composite_object) 

399 if self.severity == "warning": 

400 mh.warning(location = loc, 

401 message = self.message, 

402 explanation = self.extrainfo, 

403 user = True) 

404 else: 

405 mh.error(location = loc, 

406 message = self.message, 

407 explanation = self.extrainfo, 

408 fatal = self.severity == "fatal", 

409 user = True) 

410 return False 

411 

412 return True 

413 

414############################################################################## 

415# AST Nodes (Expressions) 

416############################################################################## 

417 

418 

419class Unary_Operator(Enum): 

420 # lobster-exclude: Utility enumeration for unary operators 

421 MINUS = auto() 

422 PLUS = auto() 

423 LOGICAL_NOT = auto() 

424 ABSOLUTE_VALUE = auto() 

425 

426 STRING_LENGTH = auto() 

427 ARRAY_LENGTH = auto() 

428 

429 CONVERSION_TO_INT = auto() 

430 CONVERSION_TO_DECIMAL = auto() 

431 

432 

433class Binary_Operator(Enum): 

434 # lobster-exclude: Utility enumeration for binary operators 

435 LOGICAL_AND = auto() # Short-circuit 

436 LOGICAL_OR = auto() # Short-circuit 

437 LOGICAL_XOR = auto() 

438 LOGICAL_IMPLIES = auto() # Short-circuit 

439 

440 COMP_EQ = auto() 

441 COMP_NEQ = auto() 

442 COMP_LT = auto() 

443 COMP_LEQ = auto() 

444 COMP_GT = auto() 

445 COMP_GEQ = auto() 

446 

447 STRING_CONTAINS = auto() 

448 STRING_STARTSWITH = auto() 

449 STRING_ENDSWITH = auto() 

450 STRING_REGEX = auto() 

451 

452 ARRAY_CONTAINS = auto() 

453 

454 PLUS = auto() 

455 MINUS = auto() 

456 TIMES = auto() 

457 DIVIDE = auto() 

458 REMAINDER = auto() 

459 

460 POWER = auto() 

461 

462 INDEX = auto() 

463 

464 

465class Expression(Node, metaclass=ABCMeta): 

466 """Abstract base class for all expressions. 

467 

468 :attribute typ: The type of this expression (or None for null values) 

469 :type: Type 

470 """ 

471 def __init__(self, location, typ): 

472 # lobster-exclude: Constructor only declares variables 

473 super().__init__(location) 

474 assert typ is None or isinstance(typ, Type) 

475 self.typ = typ 

476 

477 def evaluate(self, mh, context, gstab): # pragma: no cover 

478 """Evaluate the expression in the given context 

479 

480 The context can be None, in which case the expression is 

481 evaluated in a static context. Otherwise it must be a 

482 dictionary that maps names (such as record fields or 

483 quantified variables) to expressions. 

484 

485 The global symbol table must be None (for static context 

486 evaluations), otherwise it must contain the global symbol 

487 table to resolve record references. 

488 

489 :param mh: the message handler to use 

490 :type mh: Message_Handler 

491 :param context: name mapping or None (for a static context) 

492 :type context: dict[str, Expression] 

493 :raise TRLC_Error: if the expression cannot be evaluated 

494 :return: result of the evaluation 

495 :rtype: Value 

496 

497 """ 

498 assert isinstance(mh, Message_Handler) 

499 assert context is None or isinstance(context, dict) 

500 assert gstab is None or isinstance(gstab, Symbol_Table) 

501 assert False, "evaluate not implemented for %s" % \ 

502 self.__class__.__name__ 

503 

504 @abstractmethod 

505 def to_string(self): # pragma: no cover 

506 assert False, "to_string not implemented for %s" % \ 

507 self.__class__.__name__ 

508 

509 def ensure_type(self, mh, typ): 

510 # lobster-trace: LRM.Restricted_Null 

511 # lobster-trace: LRM.Null_Is_Invalid 

512 

513 assert isinstance(typ, (type, Type)) 

514 if self.typ is None: 

515 mh.error(self.location, 

516 "null is not permitted here") 

517 elif isinstance(typ, type) and not isinstance(self.typ, typ): 

518 mh.error(self.location, 

519 "expected expression of type %s, got %s instead" % 

520 (typ.__name__, 

521 self.typ.__class__.__name__)) 

522 elif isinstance(typ, Type) and self.typ != typ: 

523 mh.error(self.location, 

524 "expected expression of type %s, got %s instead" % 

525 (typ.name, 

526 self.typ.name)) 

527 

528 def resolve_references(self, mh): 

529 assert isinstance(mh, Message_Handler) 

530 

531 @abstractmethod 

532 def can_be_null(self): 

533 """Test if the expression could return null 

534 

535 Checks the expression if it could generate a null value 

536 *without* raising an error. For example `x` could generate a 

537 null value if `x` is a record component that is 

538 optional. However `x + 1` could not, since an error would 

539 occur earlier. 

540 

541 :return: possibility of encountering null 

542 :rtype: bool 

543 

544 """ 

545 assert False, "can_be_null not implemented for %s" % \ 

546 self.__class__.__name__ 

547 

548 

549class Implicit_Null(Expression): 

550 """Synthesised null values 

551 

552 When a record object or tuple aggregate is declared and an 

553 optional component or field is not specified, we synthesise an 

554 implicit null expression for this. 

555 

556 For example given this TRLC type:: 

557 

558 type T { 

559 x optional Integer 

560 } 

561 

562 And this declaration:: 

563 

564 T Potato {} 

565 

566 Then the field mapping for Potato will be:: 

567 

568 {x: Implicit_Null} 

569 

570 Each field will get its own implicit null. Further note that this 

571 implicit null is distinct from the explicit :class:`Null_Literal` 

572 that can appear in check expressions. 

573 

574 """ 

575 def __init__(self, composite_object, composite_component): 

576 # lobster-trace: LRM.Unspecified_Optional_Components 

577 assert isinstance(composite_object, (Record_Object, 

578 Tuple_Aggregate)) 

579 assert isinstance(composite_component, Composite_Component) 

580 super().__init__(composite_object.location, None) 

581 

582 def to_string(self): 

583 return "null" 

584 

585 def evaluate(self, mh, context, gstab): 

586 # lobster-trace: LRM.Unspecified_Optional_Components 

587 assert isinstance(mh, Message_Handler) 

588 assert context is None or isinstance(context, dict) 

589 assert gstab is None or isinstance(gstab, Symbol_Table) 

590 return Value(self.location, None, None) 

591 

592 def to_python_object(self): 

593 return None 

594 

595 def dump(self, indent=0): # pragma: no cover 

596 # lobster-exclude: Debugging feature 

597 self.write_indent(indent, "Implicit_Null") 

598 

599 def can_be_null(self): 

600 return True 

601 

602 

603class Literal(Expression, metaclass=ABCMeta): 

604 """Abstract base for all Literals 

605 

606 Does not offer any additional features, but it's a nice way to 

607 group together all literal types. This is useful if you want to 

608 check if you are dealing with a literal:: 

609 

610 isinstance(my_expression, Literal) 

611 

612 """ 

613 @abstractmethod 

614 def to_python_object(self): 

615 assert False 

616 

617 

618class Null_Literal(Literal): 

619 # lobster-trace: LRM.Primary 

620 """The null literal 

621 

622 This can appear in check expressions:: 

623 

624 a /= null implies a > 5 

625 ^^^^ 

626 

627 Please note that this is distinct from the :class:`Implicit_Null` 

628 values that appear in record objects. 

629 

630 """ 

631 def __init__(self, token): 

632 assert isinstance(token, Token) 

633 assert token.kind == "KEYWORD" 

634 assert token.value == "null" 

635 super().__init__(token.location, None) 

636 

637 def dump(self, indent=0): # pragma: no cover 

638 self.write_indent(indent, "Null Literal") 

639 

640 def to_string(self): 

641 return "null" 

642 

643 def evaluate(self, mh, context, gstab): 

644 assert isinstance(mh, Message_Handler) 

645 assert context is None or isinstance(context, dict) 

646 assert gstab is None or isinstance(gstab, Symbol_Table) 

647 return Value(self.location, None, None) 

648 

649 def to_python_object(self): 

650 return None 

651 

652 def can_be_null(self): 

653 return True 

654 

655 

656class Integer_Literal(Literal): 

657 # lobster-trace: LRM.Integer_Values 

658 # lobster-trace: LRM.Primary 

659 """Integer literals 

660 

661 Note that these are always positive. A negative integer is 

662 actually a unary negation expression, operating on a positive 

663 integer literal:: 

664 

665 x == -5 

666 

667 This would create the following tree:: 

668 

669 OP_EQUALITY 

670 NAME_REFERENCE x 

671 UNARY_EXPRESSION - 

672 INTEGER_LITERAL 5 

673 

674 :attribute value: the non-negative integer value 

675 :type: int 

676 """ 

677 def __init__(self, token, typ): 

678 assert isinstance(token, Token) 

679 assert token.kind == "INTEGER" 

680 assert isinstance(typ, Builtin_Integer) 

681 super().__init__(token.location, typ) 

682 

683 self.value = token.value 

684 

685 def dump(self, indent=0): # pragma: no cover 

686 self.write_indent(indent, f"Integer Literal {self.value}") 

687 

688 def to_string(self): 

689 return str(self.value) 

690 

691 def evaluate(self, mh, context, gstab): 

692 assert isinstance(mh, Message_Handler) 

693 assert context is None or isinstance(context, dict) 

694 assert gstab is None or isinstance(gstab, Symbol_Table) 

695 return Value(self.location, self.value, self.typ) 

696 

697 def to_python_object(self): 

698 return self.value 

699 

700 def can_be_null(self): 

701 return False 

702 

703 

704class Decimal_Literal(Literal): 

705 # lobster-trace: LRM.Decimal_Values 

706 # lobster-trace: LRM.Primary 

707 """Decimal literals 

708 

709 Note that these are always positive. A negative decimal is 

710 actually a unary negation expression, operating on a positive 

711 decimal literal:: 

712 

713 x == -5.0 

714 

715 This would create the following tree:: 

716 

717 OP_EQUALITY 

718 NAME_REFERENCE x 

719 UNARY_EXPRESSION - 

720 DECIMAL_LITERAL 5.0 

721 

722 :attribute value: the non-negative decimal value 

723 :type: fractions.Fraction 

724 """ 

725 def __init__(self, token, typ): 

726 assert isinstance(token, Token) 

727 assert token.kind == "DECIMAL" 

728 assert isinstance(typ, Builtin_Decimal) 

729 super().__init__(token.location, typ) 

730 

731 self.value = token.value 

732 

733 def dump(self, indent=0): # pragma: no cover 

734 self.write_indent(indent, f"Decimal Literal {self.value}") 

735 

736 def to_string(self): 

737 return str(self.value) 

738 

739 def evaluate(self, mh, context, gstab): 

740 assert isinstance(mh, Message_Handler) 

741 assert context is None or isinstance(context, dict) 

742 assert gstab is None or isinstance(gstab, Symbol_Table) 

743 return Value(self.location, self.value, self.typ) 

744 

745 def to_python_object(self): 

746 return float(self.value) 

747 

748 def can_be_null(self): 

749 return False 

750 

751 

752class String_Literal(Literal): 

753 # lobster-trace: LRM.String_Values 

754 # lobster-trace: LRM.Markup_String_Values 

755 # lobster-trace: LRM.Primary 

756 """String literals 

757 

758 Note the value of the string does not include the quotation marks, 

759 and any escape sequences are fully resolved. For example:: 

760 

761 "foo\\"bar" 

762 

763 Will have a value of ``foo"bar``. 

764 

765 :attribute value: string content 

766 :type: str 

767 

768 :attribute references: resolved references of a markup string 

769 :type: list[Record_Reference] 

770 

771 """ 

772 def __init__(self, token, typ): 

773 assert isinstance(token, Token) 

774 assert token.kind == "STRING" 

775 assert isinstance(typ, Builtin_String) 

776 super().__init__(token.location, typ) 

777 

778 self.value = token.value 

779 self.has_references = isinstance(typ, Builtin_Markup_String) 

780 self.references = [] 

781 

782 def dump(self, indent=0): # pragma: no cover 

783 self.write_indent(indent, f"String Literal {repr(self.value)}") 

784 if self.has_references: 

785 self.write_indent(indent + 1, "Markup References") 

786 for ref in self.references: 

787 ref.dump(indent + 2) 

788 

789 def to_string(self): 

790 return self.value 

791 

792 def evaluate(self, mh, context, gstab): 

793 assert isinstance(mh, Message_Handler) 

794 assert context is None or isinstance(context, dict) 

795 assert gstab is None or isinstance(gstab, Symbol_Table) 

796 return Value(self.location, self.value, self.typ) 

797 

798 def to_python_object(self): 

799 return self.value 

800 

801 def resolve_references(self, mh): 

802 assert isinstance(mh, Message_Handler) 

803 for ref in self.references: 

804 ref.resolve_references(mh) 

805 

806 def can_be_null(self): 

807 return False 

808 

809 

810class Boolean_Literal(Literal): 

811 # lobster-trace: LRM.Boolean_Values 

812 # lobster-trace: LRM.Primary 

813 """Boolean values 

814 

815 :attribute value: the boolean value 

816 :type: bool 

817 """ 

818 def __init__(self, token, typ): 

819 assert isinstance(token, Token) 

820 assert token.kind == "KEYWORD" 

821 assert token.value in ("false", "true") 

822 assert isinstance(typ, Builtin_Boolean) 

823 super().__init__(token.location, typ) 

824 

825 self.value = token.value == "true" 

826 

827 def dump(self, indent=0): # pragma: no cover 

828 self.write_indent(indent, f"Boolean Literal {self.value}") 

829 

830 def to_string(self): 

831 return str(self.value) 

832 

833 def evaluate(self, mh, context, gstab): 

834 assert isinstance(mh, Message_Handler) 

835 assert context is None or isinstance(context, dict) 

836 assert gstab is None or isinstance(gstab, Symbol_Table) 

837 return Value(self.location, self.value, self.typ) 

838 

839 def to_python_object(self): 

840 return self.value 

841 

842 def can_be_null(self): 

843 return False 

844 

845 

846class Enumeration_Literal(Literal): 

847 """Enumeration values 

848 

849 Note that this is distinct from 

850 :class:`Enumeration_Literal_Spec`. An enumeration literal is a 

851 specific mention of an enumeration member in an expression:: 

852 

853 foo != my_enum.POTATO 

854 ^^^^^^^^^^^^^^ 

855 

856 To get to the string value of the enumeration literal 

857 (i.e. ``POTATO`` here) you can get the name of the literal spec 

858 itself: ``enum_lit.value.name``; and to get the name of the 

859 enumeration (i.e. ``my_enum`` here) you can use 

860 ``enum_lit.value.n_typ.name``. 

861 

862 :attribute value: enumeration value 

863 :type: Enumeration_Literal_Spec 

864 

865 """ 

866 def __init__(self, location, literal): 

867 # lobster-exclude: Constructor only declares variables 

868 assert isinstance(literal, Enumeration_Literal_Spec) 

869 super().__init__(location, literal.n_typ) 

870 

871 self.value = literal 

872 

873 def dump(self, indent=0): # pragma: no cover 

874 # lobster-exclude: Debugging feature 

875 self.write_indent(indent, 

876 f"Enumeration Literal {self.typ.name}.{self.value.name}") 

877 

878 def to_string(self): 

879 return self.typ.name + "." + self.value.name 

880 

881 def evaluate(self, mh, context, gstab): 

882 assert isinstance(mh, Message_Handler) 

883 assert context is None or isinstance(context, dict) 

884 assert gstab is None or isinstance(gstab, Symbol_Table) 

885 return Value(self.location, self.value, self.typ) 

886 

887 def to_python_object(self): 

888 return self.value.name 

889 

890 def can_be_null(self): 

891 return False 

892 

893 

894class Array_Aggregate(Expression): 

895 """Instances of array types 

896 

897 This is created when assigning to array components:: 

898 

899 potatoes = ["picasso", "yukon gold", "sweet"] 

900 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 

901 

902 The type of expression that can be found in an array is somewhat 

903 limited: 

904 

905 * :class:`Literal` 

906 * :class:`Array_Aggregate` 

907 * :class:`Record_Reference` 

908 

909 :attribute value: contents of the array 

910 :type: list[Expression] 

911 

912 """ 

913 def __init__(self, location, typ): 

914 # lobster-trace: LRM.Record_Object_Declaration 

915 

916 super().__init__(location, typ) 

917 self.value = [] 

918 

919 def dump(self, indent=0): # pragma: no cover 

920 # lobster-exclude: Debugging feature 

921 self.write_indent(indent, "Array_Aggregate") 

922 for n_value in self.value: 

923 n_value.dump(indent + 1) 

924 

925 def append(self, value): 

926 assert isinstance(value, (Literal, 

927 Unary_Expression, 

928 Array_Aggregate, 

929 Tuple_Aggregate, 

930 Record_Reference)) 

931 self.value.append(value) 

932 

933 def to_string(self): 

934 return "[" + ", ".join(x.to_string() for x in self.value) + "]" 

935 

936 def evaluate(self, mh, context, gstab): 

937 assert isinstance(mh, Message_Handler) 

938 assert context is None or isinstance(context, dict) 

939 assert gstab is None or isinstance(gstab, Symbol_Table) 

940 return Value(self.location, 

941 list(element.evaluate(mh, context, gstab) 

942 for element in self.value), 

943 self.typ) 

944 

945 def resolve_references(self, mh): 

946 assert isinstance(mh, Message_Handler) 

947 

948 for val in self.value: 

949 val.resolve_references(mh) 

950 

951 def to_python_object(self): 

952 return [x.to_python_object() for x in self.value] 

953 

954 def can_be_null(self): 

955 return False 

956 

957 

958class Tuple_Aggregate(Expression): 

959 """Instances of a tuple 

960 

961 This is created when assigning to a tuple components. There are 

962 two forms, the ordinary form:: 

963 

964 coordinate = (12.3, 40.0) 

965 ^^^^^^^^^^^^ 

966 

967 And the separator form:: 

968 

969 item = 12345@42 

970 ^^^^^^^^ 

971 

972 In terms of AST there is no difference, as the separator is only 

973 syntactic sugar. 

974 

975 :attribute value: contents of the tuple 

976 :type: dict[str, Expression] 

977 

978 """ 

979 def __init__(self, location, typ): 

980 # lobster-trace: LRM.Unspecified_Optional_Components 

981 # lobster-trace: LRM.Record_Object_Declaration 

982 

983 super().__init__(location, typ) 

984 self.value = {n_field.name : Implicit_Null(self, n_field) 

985 for n_field in self.typ.components.values()} 

986 

987 def assign(self, field, value): 

988 assert isinstance(field, str) 

989 assert isinstance(value, (Literal, 

990 Unary_Expression, 

991 Tuple_Aggregate, 

992 Record_Reference)), \ 

993 "value is %s" % value.__class__.__name__ 

994 assert field in self.typ.components 

995 

996 self.value[field] = value 

997 

998 def dump(self, indent=0): # pragma: no cover 

999 # lobster-exclude: Debugging feature 

1000 self.write_indent(indent, "Tuple_Aggregate") 

1001 self.write_indent(indent + 1, f"Type: {self.typ.name}") 

1002 for n_item in self.typ.iter_sequence(): 

1003 if isinstance(n_item, Composite_Component): 

1004 self.value[n_item.name].dump(indent + 1) 

1005 

1006 def to_string(self): 

1007 first = True 

1008 if self.typ.has_separators(): 

1009 rv = "" 

1010 else: 

1011 rv = "(" 

1012 for n_item in self.typ.iter_sequence(): 

1013 if isinstance(n_item, Separator): 

1014 rv += " %s " % n_item.token.value 

1015 elif first: 

1016 first = False 

1017 else: 

1018 rv += ", " 

1019 

1020 if isinstance(n_item, Composite_Component): 

1021 rv += self.value[n_item.name].to_string() 

1022 if self.typ.has_separators(): 

1023 rv = "" 

1024 else: 

1025 rv = ")" 

1026 return rv 

1027 

1028 def evaluate(self, mh, context, gstab): 

1029 assert isinstance(mh, Message_Handler) 

1030 assert context is None or isinstance(context, dict) 

1031 assert gstab is None or isinstance(gstab, Symbol_Table) 

1032 return Value(self.location, 

1033 {name : element.evaluate(mh, context, gstab) 

1034 for name, element in self.value.items()}, 

1035 self.typ) 

1036 

1037 def resolve_references(self, mh): 

1038 assert isinstance(mh, Message_Handler) 

1039 

1040 for val in self.value.values(): 

1041 val.resolve_references(mh) 

1042 

1043 def to_python_object(self): 

1044 return {name: value.to_python_object() 

1045 for name, value in self.value.items()} 

1046 

1047 def can_be_null(self): 

1048 return False 

1049 

1050 

1051class Record_Reference(Expression): 

1052 """Reference to another record object 

1053 

1054 This can appear in record object declarations:: 

1055 

1056 Requirement Kitten { 

1057 depends_on = Other_Package.Cat 

1058 ^1 ^2 

1059 } 

1060 

1061 Note that this is distinct from :class:`Record_Object`. It is just 

1062 the name; to get to the object referred to by this you can consult 

1063 the target attribute. 

1064 

1065 The reason we have this indirection is that not all names can be 

1066 immediately resolved on parsing in the TRLC language. 

1067 

1068 Note that while the containing package (see 1) is optional in the 

1069 source language, the containing package will always be filled in 

1070 in this AST node. 

1071 

1072 :attribute name: The name of the record (see 2) 

1073 :type: str 

1074 

1075 :attribute target: The concrete record object referred to by (2) 

1076 :type: Record_Object 

1077 

1078 :attribute package: The package (see 1) supposed to contain (2) 

1079 :type: Package 

1080 

1081 """ 

1082 def __init__(self, location, name, typ, package): 

1083 # lobster-exclude: Constructor only declares variables 

1084 assert isinstance(location, Location) 

1085 assert isinstance(name, str) 

1086 assert isinstance(typ, (Record_Type, Union_Type)) or typ is None 

1087 assert isinstance(package, Package) 

1088 super().__init__(location, typ) 

1089 

1090 self.name = name 

1091 self.target = None 

1092 self.package = package 

1093 

1094 def dump(self, indent=0): # pragma: no cover 

1095 # lobster-exclude: Debugging feature 

1096 self.write_indent(indent, f"Record Reference {self.name}") 

1097 self.write_indent(indent + 1, 

1098 f"Resolved: {self.target is not None}") 

1099 

1100 def to_string(self): 

1101 return self.name 

1102 

1103 def evaluate(self, mh, context, gstab): 

1104 assert isinstance(mh, Message_Handler) 

1105 assert context is None or isinstance(context, dict) 

1106 assert gstab is None or isinstance(gstab, Symbol_Table) 

1107 return Value(self.location, copy(self.target.field), self.typ) 

1108 

1109 def resolve_references(self, mh): 

1110 # lobster-trace: LRM.References_To_Extensions 

1111 # lobster-trace: LRM.Union_Type_Minimum_Members 

1112 assert isinstance(mh, Message_Handler) 

1113 

1114 self.target = self.package.symbols.lookup_direct( 

1115 mh = mh, 

1116 name = self.name, 

1117 error_location = self.location, 

1118 required_subclass = Record_Object) 

1119 if self.typ is None: 

1120 self.typ = self.target.n_typ 

1121 elif isinstance(self.typ, Union_Type): 

1122 if not self.typ.is_compatible(self.target.n_typ): 

1123 mh.error(self.location, 

1124 "expected reference of type %s," 

1125 " but %s is of type %s" % 

1126 (self.typ.name, 

1127 self.target.name, 

1128 self.target.n_typ.name)) 

1129 elif not self.target.n_typ.is_subclass_of(self.typ): 

1130 mh.error(self.location, 

1131 "expected reference of type %s, but %s is of type %s" % 

1132 (self.typ.name, 

1133 self.target.name, 

1134 self.target.n_typ.name)) 

1135 

1136 def to_python_object(self): 

1137 return self.target.fully_qualified_name() 

1138 

1139 def can_be_null(self): 

1140 return False 

1141 

1142 

1143class Name_Reference(Expression): 

1144 # lobster-trace: LRM.Qualified_Name 

1145 # lobster-trace: LRM.Static_Regular_Expression 

1146 

1147 """Reference to a name 

1148 

1149 Name reference to either a :class:`Composite_Component` or a 

1150 :class:`Quantified_Variable`. The actual value of course depends 

1151 on the context. See :py:meth:`Expression.evaluate()`. 

1152 

1153 For example:: 

1154 

1155 (forall x in potato => x > 1) 

1156 ^1 ^2 

1157 

1158 Both indicated parts are a :class:`Name_Reference`, the first one 

1159 refers to a :class:`Composite_Component`, and the second refers to a 

1160 :class:`Quantified_Variable`. 

1161 

1162 :attribute entity: the entity named here 

1163 :type: Composite_Component, Quantified_Variable 

1164 """ 

1165 def __init__(self, location, entity): 

1166 assert isinstance(entity, (Composite_Component, 

1167 Quantified_Variable)) 

1168 super().__init__(location, entity.n_typ) 

1169 self.entity = entity 

1170 

1171 def dump(self, indent=0): # pragma: no cover 

1172 self.write_indent(indent, f"Name Reference to {self.entity.name}") 

1173 

1174 def to_string(self): 

1175 return self.entity.name 

1176 

1177 def evaluate(self, mh, context, gstab): 

1178 assert isinstance(mh, Message_Handler) 

1179 assert context is None or isinstance(context, dict) 

1180 assert gstab is None or isinstance(gstab, Symbol_Table) 

1181 

1182 if context is None: 

1183 mh.error(self.location, 

1184 "cannot be used in a static context") 

1185 

1186 assert self.entity.name in context 

1187 return context[self.entity.name].evaluate(mh, context, gstab) 

1188 

1189 def can_be_null(self): 

1190 # The only way we could generate null here (without raising 

1191 # error earlier) is when we refer to a component that is 

1192 # optional. 

1193 if isinstance(self.entity, Composite_Component): 

1194 return self.entity.optional 

1195 else: 

1196 return False 

1197 

1198 

1199class Unary_Expression(Expression): 

1200 """Expression with only one operand 

1201 

1202 This captures the following operations: 

1203 

1204 * Unary_Operator.PLUS (e.g. ``+5``) 

1205 * Unary_Operator.MINUS (e.g. ``-5``) 

1206 * Unary_Operator.ABSOLUTE_VALUE (e.g. ``abs 42``) 

1207 * Unary_Operator.LOGICAL_NOT (e.g. ``not True``) 

1208 * Unary_Operator.STRING_LENGTH (e.g. ``len("foobar")``) 

1209 * Unary_Operator.ARRAY_LENGTH (e.g. ``len(component_name)``) 

1210 * Unary_Operator.CONVERSION_TO_INT (e.g. ``Integer(5.3)``) 

1211 * Unary_Operator.CONVERSION_TO_DECIMAL (e.g. ``Decimal(5)``) 

1212 

1213 Note that several builtin functions are mapped to unary operators. 

1214 

1215 :attribute operator: the operation 

1216 :type: Unary_Operator 

1217 

1218 :attribute n_operand: the expression we operate on 

1219 :type: Expression 

1220 

1221 """ 

1222 def __init__(self, mh, location, typ, operator, n_operand): 

1223 # lobster-trace: LRM.Simple_Expression 

1224 # lobster-trace: LRM.Relation 

1225 # lobster-trace: LRM.Factor 

1226 # lobster-trace: LRM.Signature_Len 

1227 # lobster-trace: LRM.Signature_Type_Conversion 

1228 

1229 super().__init__(location, typ) 

1230 assert isinstance(mh, Message_Handler) 

1231 assert isinstance(operator, Unary_Operator) 

1232 assert isinstance(n_operand, Expression) 

1233 self.operator = operator 

1234 self.n_operand = n_operand 

1235 

1236 if operator in (Unary_Operator.MINUS, 

1237 Unary_Operator.PLUS, 

1238 Unary_Operator.ABSOLUTE_VALUE): 

1239 self.n_operand.ensure_type(mh, Builtin_Numeric_Type) 

1240 elif operator == Unary_Operator.LOGICAL_NOT: 

1241 self.n_operand.ensure_type(mh, Builtin_Boolean) 

1242 elif operator == Unary_Operator.STRING_LENGTH: 

1243 self.n_operand.ensure_type(mh, Builtin_String) 

1244 elif operator == Unary_Operator.ARRAY_LENGTH: 

1245 self.n_operand.ensure_type(mh, Array_Type) 

1246 elif operator == Unary_Operator.CONVERSION_TO_INT: 

1247 self.n_operand.ensure_type(mh, Builtin_Numeric_Type) 

1248 elif operator == Unary_Operator.CONVERSION_TO_DECIMAL: 

1249 self.n_operand.ensure_type(mh, Builtin_Numeric_Type) 

1250 else: 

1251 mh.ice_loc(self.location, 

1252 "unexpected unary operation %s" % operator) 

1253 

1254 def to_string(self): 

1255 prefix_operators = { 

1256 Unary_Operator.MINUS : "-", 

1257 Unary_Operator.PLUS : "+", 

1258 Unary_Operator.ABSOLUTE_VALUE : "abs ", 

1259 Unary_Operator.LOGICAL_NOT : "not ", 

1260 } 

1261 function_calls = { 

1262 Unary_Operator.STRING_LENGTH : "len", 

1263 Unary_Operator.ARRAY_LENGTH : "len", 

1264 Unary_Operator.CONVERSION_TO_INT : "Integer", 

1265 Unary_Operator.CONVERSION_TO_DECIMAL : "Decimal" 

1266 } 

1267 

1268 if self.operator in prefix_operators: 

1269 return prefix_operators[self.operator] + \ 

1270 self.n_operand.to_string() 

1271 

1272 elif self.operator in function_calls: 

1273 return "%s(%s)" % (function_calls[self.operator], 

1274 self.n_operand.to_string()) 

1275 

1276 else: 

1277 assert False 

1278 

1279 def dump(self, indent=0): # pragma: no cover 

1280 # lobster-exclude: Debugging feature 

1281 self.write_indent(indent, f"Unary {self.operator} Expression") 

1282 self.write_indent(indent + 1, f"Type: {self.typ.name}") 

1283 self.n_operand.dump(indent + 1) 

1284 

1285 def evaluate(self, mh, context, gstab): 

1286 # lobster-trace: LRM.Null_Is_Invalid 

1287 # lobster-trace: LRM.Signature_Len 

1288 # lobster-trace: LRM.Signature_Type_Conversion 

1289 # lobster-trace: LRM.Len_Semantics 

1290 # lobster-trace: LRM.Integer_Conversion_Semantics 

1291 # lobster-trace: LRM.Decimal_Conversion_Semantics 

1292 

1293 assert isinstance(mh, Message_Handler) 

1294 assert context is None or isinstance(context, dict) 

1295 assert gstab is None or isinstance(gstab, Symbol_Table) 

1296 

1297 v_operand = self.n_operand.evaluate(mh, context, gstab) 

1298 if v_operand.value is None: 1298 ↛ 1299line 1298 didn't jump to line 1299 because the condition on line 1298 was never true

1299 mh.error(v_operand.location, 

1300 "input to unary expression %s (%s) must not be null" % 

1301 (self.to_string(), 

1302 mh.cross_file_reference(self.location))) 

1303 

1304 if self.operator == Unary_Operator.MINUS: 

1305 return Value(location = self.location, 

1306 value = -v_operand.value, 

1307 typ = self.typ) 

1308 elif self.operator == Unary_Operator.PLUS: 

1309 return Value(location = self.location, 

1310 value = +v_operand.value, 

1311 typ = self.typ) 

1312 elif self.operator == Unary_Operator.LOGICAL_NOT: 

1313 return Value(location = self.location, 

1314 value = not v_operand.value, 

1315 typ = self.typ) 

1316 elif self.operator == Unary_Operator.ABSOLUTE_VALUE: 

1317 return Value(location = self.location, 

1318 value = abs(v_operand.value), 

1319 typ = self.typ) 

1320 elif self.operator in (Unary_Operator.STRING_LENGTH, 

1321 Unary_Operator.ARRAY_LENGTH): 

1322 return Value(location = self.location, 

1323 value = len(v_operand.value), 

1324 typ = self.typ) 

1325 elif self.operator == Unary_Operator.CONVERSION_TO_INT: 

1326 if isinstance(v_operand.value, Fraction): 1326 ↛ 1332line 1326 didn't jump to line 1332 because the condition on line 1326 was always true

1327 return Value( 

1328 location = self.location, 

1329 value = math.round_nearest_away(v_operand.value), 

1330 typ = self.typ) 

1331 else: 

1332 return Value(location = self.location, 

1333 value = v_operand.value, 

1334 typ = self.typ) 

1335 elif self.operator == Unary_Operator.CONVERSION_TO_DECIMAL: 

1336 return Value(location = self.location, 

1337 value = Fraction(v_operand.value), 

1338 typ = self.typ) 

1339 else: 

1340 mh.ice_loc(self.location, 

1341 "unexpected unary operation %s" % self.operator) 

1342 

1343 def to_python_object(self): 

1344 assert self.operator in (Unary_Operator.MINUS, 

1345 Unary_Operator.PLUS) 

1346 val = self.n_operand.to_python_object() 

1347 if self.operator == Unary_Operator.MINUS: 

1348 return -val 

1349 else: 

1350 return val 

1351 

1352 def can_be_null(self): 

1353 return False 

1354 

1355 

1356class Binary_Expression(Expression): 

1357 """Expression with two operands 

1358 

1359 This captures the following operations: 

1360 

1361 * Binary_Operator.LOGICAL_AND (e.g. ``a and b``) 

1362 * Binary_Operator.LOGICAL_OR (e.g. ``a or b``) 

1363 * Binary_Operator.LOGICAL_XOR (e.g. ``a xor b``) 

1364 * Binary_Operator.LOGICAL_IMPLIES (e.g. ``a implies b``) 

1365 * Binary_Operator.COMP_EQ (e.g. ``a == null``) 

1366 * Binary_Operator.COMP_NEQ (e.g. ``a != null``) 

1367 * Binary_Operator.COMP_LT (e.g. ``1 < 2``) 

1368 * Binary_Operator.COMP_LEQ (e.g. ``1 <= 2``) 

1369 * Binary_Operator.COMP_GT (e.g. ``a > b``) 

1370 * Binary_Operator.COMP_GEQ (e.g. ``a >= b``) 

1371 * Binary_Operator.STRING_CONTAINS (e.g. ``"foo" in "foobar"``) 

1372 * Binary_Operator.STRING_STARTSWITH (e.g. ``startswith("foo", "f")``) 

1373 * Binary_Operator.STRING_ENDSWITH (e.g. ``endswith("foo", "o")``) 

1374 * Binary_Operator.STRING_REGEX (e.g. ``matches("foo", ".o.``) 

1375 * Binary_Operator.ARRAY_CONTAINS (e.g. ``42 in arr``) 

1376 * Binary_Operator.PLUS (e.g. ``42 + b`` or ``"foo" + bar``) 

1377 * Binary_Operator.MINUS (e.g. ``a - 1``) 

1378 * Binary_Operator.TIMES (e.g. ``2 * x``) 

1379 * Binary_Operator.DIVIDE (e.g. ``x / 2``) 

1380 * Binary_Operator.REMAINDER (e.g. ``x % 2``) 

1381 * Binary_Operator.POWER (e.g. ``x ** 2``) 

1382 * Binary_Operator.INDEX (e.g. ``foo[2]``) 

1383 

1384 Note that several builtin functions are mapped to unary operators. 

1385 

1386 Note also that the plus operation is supported for integers, 

1387 rationals and strings. 

1388 

1389 :attribute operator: the operation 

1390 :type: Binary_Operator 

1391 

1392 :attribute n_lhs: the first operand 

1393 :type: Expression 

1394 

1395 :attribute n_rhs: the second operand 

1396 :type: Expression 

1397 

1398 """ 

1399 def __init__(self, mh, location, typ, operator, n_lhs, n_rhs): 

1400 # lobster-trace: LRM.Expression 

1401 # lobster-trace: LRM.Relation 

1402 # lobster-trace: LRM.Simple_Expression 

1403 # lobster-trace: LRM.Term 

1404 # lobster-trace: LRM.Factor 

1405 # lobster-trace: LRM.Signature_String_End_Functions 

1406 # lobster-trace: LRM.Signature_Matches 

1407 

1408 super().__init__(location, typ) 

1409 assert isinstance(mh, Message_Handler) 

1410 assert isinstance(operator, Binary_Operator) 

1411 assert isinstance(n_lhs, Expression) 

1412 assert isinstance(n_rhs, Expression) 

1413 self.operator = operator 

1414 self.n_lhs = n_lhs 

1415 self.n_rhs = n_rhs 

1416 

1417 if operator in (Binary_Operator.LOGICAL_AND, 

1418 Binary_Operator.LOGICAL_OR, 

1419 Binary_Operator.LOGICAL_XOR, 

1420 Binary_Operator.LOGICAL_IMPLIES): 

1421 self.n_lhs.ensure_type(mh, Builtin_Boolean) 

1422 self.n_rhs.ensure_type(mh, Builtin_Boolean) 

1423 

1424 elif operator in (Binary_Operator.COMP_EQ, 

1425 Binary_Operator.COMP_NEQ): 

1426 # lobster-trace: LRM.Union_Type_Equality 

1427 # lobster-trace: LRM.Union_Type_Equality_Domain 

1428 if (self.n_lhs.typ is None) or (self.n_rhs.typ is None): 

1429 # We can compary anything to null (including itself) 

1430 pass 

1431 elif isinstance(self.n_lhs.typ, Union_Type) or \ 

1432 isinstance(self.n_rhs.typ, Union_Type): 

1433 # For union types, we allow comparison if both 

1434 # sides are record-like (Record_Type or Union_Type) 

1435 lhs_is_record = isinstance(self.n_lhs.typ, 

1436 (Record_Type, Union_Type)) 

1437 rhs_is_record = isinstance(self.n_rhs.typ, 

1438 (Record_Type, Union_Type)) 

1439 if not (lhs_is_record and rhs_is_record): 1439 ↛ 1440line 1439 didn't jump to line 1440 because the condition on line 1439 was never true

1440 mh.error(self.location, 

1441 "type mismatch: %s and %s do not match" % 

1442 (self.n_lhs.typ.name, 

1443 self.n_rhs.typ.name)) 

1444 else: 

1445 # Check that there is at least one pair of member 

1446 # types (one from each side) where one is a subtype 

1447 # of the other. This implements Equality_Domain for 

1448 # union types: an unrelated record type is rejected. 

1449 lhs_members = (self.n_lhs.typ.types 

1450 if isinstance(self.n_lhs.typ, Union_Type) 

1451 else [self.n_lhs.typ]) 

1452 rhs_members = (self.n_rhs.typ.types 

1453 if isinstance(self.n_rhs.typ, Union_Type) 

1454 else [self.n_rhs.typ]) 

1455 if not any(lm.is_subclass_of(rm) or rm.is_subclass_of(lm) 

1456 for lm in lhs_members 

1457 for rm in rhs_members): 

1458 mh.error(self.location, 

1459 "type mismatch: %s and %s do not match" % 

1460 (self.n_lhs.typ.name, 

1461 self.n_rhs.typ.name)) 

1462 elif self.n_lhs.typ != self.n_rhs.typ: 

1463 # Otherwise we can compare anything, as long as the 

1464 # types match 

1465 mh.error(self.location, 

1466 "type mismatch: %s and %s do not match" % 

1467 (self.n_lhs.typ.name, 

1468 self.n_rhs.typ.name)) 

1469 

1470 elif operator in (Binary_Operator.COMP_LT, 

1471 Binary_Operator.COMP_LEQ, 

1472 Binary_Operator.COMP_GT, 

1473 Binary_Operator.COMP_GEQ): 

1474 self.n_lhs.ensure_type(mh, Builtin_Numeric_Type) 

1475 self.n_rhs.ensure_type(mh, self.n_lhs.typ) 

1476 

1477 elif operator in (Binary_Operator.STRING_CONTAINS, 

1478 Binary_Operator.STRING_STARTSWITH, 

1479 Binary_Operator.STRING_ENDSWITH, 

1480 Binary_Operator.STRING_REGEX): 

1481 self.n_lhs.ensure_type(mh, Builtin_String) 

1482 self.n_rhs.ensure_type(mh, Builtin_String) 

1483 

1484 elif operator == Binary_Operator.ARRAY_CONTAINS: 

1485 self.n_rhs.ensure_type(mh, Array_Type) 

1486 self.n_lhs.ensure_type(mh, self.n_rhs.typ.element_type.__class__) 

1487 

1488 elif operator == Binary_Operator.PLUS: 

1489 if isinstance(self.n_lhs.typ, Builtin_Numeric_Type): 

1490 self.n_rhs.ensure_type(mh, self.n_lhs.typ) 

1491 else: 

1492 self.n_lhs.ensure_type(mh, Builtin_String) 

1493 self.n_rhs.ensure_type(mh, Builtin_String) 

1494 

1495 elif operator in (Binary_Operator.MINUS, 

1496 Binary_Operator.TIMES, 

1497 Binary_Operator.DIVIDE): 

1498 self.n_lhs.ensure_type(mh, Builtin_Numeric_Type) 

1499 self.n_rhs.ensure_type(mh, self.n_lhs.typ) 

1500 

1501 elif operator == Binary_Operator.POWER: 

1502 self.n_lhs.ensure_type(mh, Builtin_Numeric_Type) 

1503 self.n_rhs.ensure_type(mh, Builtin_Integer) 

1504 

1505 elif operator == Binary_Operator.REMAINDER: 

1506 self.n_lhs.ensure_type(mh, Builtin_Integer) 

1507 self.n_rhs.ensure_type(mh, Builtin_Integer) 

1508 

1509 elif operator == Binary_Operator.INDEX: 

1510 self.n_lhs.ensure_type(mh, Array_Type) 

1511 self.n_rhs.ensure_type(mh, Builtin_Integer) 

1512 

1513 else: 

1514 mh.ice_loc(self.location, 

1515 "unexpected binary operation %s" % operator) 

1516 

1517 def dump(self, indent=0): # pragma: no cover 

1518 # lobster-exclude: Debugging feature 

1519 self.write_indent(indent, f"Binary {self.operator} Expression") 

1520 self.write_indent(indent + 1, f"Type: {self.typ.name}") 

1521 self.n_lhs.dump(indent + 1) 

1522 self.n_rhs.dump(indent + 1) 

1523 

1524 def to_string(self): 

1525 infix_operators = { 

1526 Binary_Operator.LOGICAL_AND : "and", 

1527 Binary_Operator.LOGICAL_OR : "or", 

1528 Binary_Operator.LOGICAL_XOR : "xor", 

1529 Binary_Operator.LOGICAL_IMPLIES : "implies", 

1530 Binary_Operator.COMP_EQ : "==", 

1531 Binary_Operator.COMP_NEQ : "!=", 

1532 Binary_Operator.COMP_LT : "<", 

1533 Binary_Operator.COMP_LEQ : "<=", 

1534 Binary_Operator.COMP_GT : ">", 

1535 Binary_Operator.COMP_GEQ : ">=", 

1536 Binary_Operator.STRING_CONTAINS : "in", 

1537 Binary_Operator.ARRAY_CONTAINS : "in", 

1538 Binary_Operator.PLUS : "+", 

1539 Binary_Operator.MINUS : "-", 

1540 Binary_Operator.TIMES : "*", 

1541 Binary_Operator.DIVIDE : "/", 

1542 Binary_Operator.REMAINDER : "%", 

1543 Binary_Operator.POWER : "**", 

1544 } 

1545 string_functions = { 

1546 Binary_Operator.STRING_STARTSWITH : "startswith", 

1547 Binary_Operator.STRING_ENDSWITH : "endswith", 

1548 Binary_Operator.STRING_REGEX : "matches", 

1549 } 

1550 

1551 if self.operator in infix_operators: 

1552 return "%s %s %s" % (self.n_lhs.to_string(), 

1553 infix_operators[self.operator], 

1554 self.n_rhs.to_string()) 

1555 

1556 elif self.operator in string_functions: 

1557 return "%s(%s, %s)" % (string_functions[self.operator], 

1558 self.n_lhs.to_string(), 

1559 self.n_rhs.to_string()) 

1560 

1561 elif self.operator == Binary_Operator.INDEX: 

1562 return "%s[%s]" % (self.n_lhs.to_string(), 

1563 self.n_rhs.to_string()) 

1564 

1565 else: 

1566 assert False 

1567 

1568 def evaluate(self, mh, context, gstab): 

1569 # lobster-trace: LRM.Null_Equivalence 

1570 # lobster-trace: LRM.Null_Is_Invalid 

1571 # lobster-trace: LRM.Signature_String_End_Functions 

1572 # lobster-trace: LRM.Signature_Matches 

1573 # lobster-trace: LRM.Startswith_Semantics 

1574 # lobster-trace: LRM.Endswith_Semantics 

1575 # lobster-trace: LRM.Matches_Semantics 

1576 

1577 assert isinstance(mh, Message_Handler) 

1578 assert context is None or isinstance(context, dict) 

1579 assert gstab is None or isinstance(gstab, Symbol_Table) 

1580 

1581 v_lhs = self.n_lhs.evaluate(mh, context, gstab) 

1582 if v_lhs.value is None and \ 

1583 self.operator not in (Binary_Operator.COMP_EQ, 

1584 Binary_Operator.COMP_NEQ): 

1585 mh.error(v_lhs.location, 

1586 "lhs of check %s (%s) must not be null" % 

1587 (self.to_string(), 

1588 mh.cross_file_reference(self.location))) 

1589 

1590 # Check for the short-circuit operators first 

1591 if self.operator == Binary_Operator.LOGICAL_AND: 

1592 assert isinstance(v_lhs.value, bool) 

1593 if v_lhs.value: 

1594 return self.n_rhs.evaluate(mh, context, gstab) 

1595 else: 

1596 return v_lhs 

1597 

1598 elif self.operator == Binary_Operator.LOGICAL_OR: 

1599 assert isinstance(v_lhs.value, bool) 

1600 if v_lhs.value: 

1601 return v_lhs 

1602 else: 

1603 return self.n_rhs.evaluate(mh, context, gstab) 

1604 

1605 elif self.operator == Binary_Operator.LOGICAL_IMPLIES: 

1606 assert isinstance(v_lhs.value, bool) 

1607 if v_lhs.value: 

1608 return self.n_rhs.evaluate(mh, context, gstab) 

1609 else: 

1610 return Value(location = self.location, 

1611 value = True, 

1612 typ = self.typ) 

1613 

1614 # Otherwise, evaluate RHS and do the operation 

1615 v_rhs = self.n_rhs.evaluate(mh, context, gstab) 

1616 if v_rhs.value is None and \ 

1617 self.operator not in (Binary_Operator.COMP_EQ, 

1618 Binary_Operator.COMP_NEQ): 

1619 mh.error(v_rhs.location, 

1620 "rhs of check %s (%s) must not be null" % 

1621 (self.to_string(), 

1622 mh.cross_file_reference(self.location))) 

1623 

1624 if self.operator == Binary_Operator.LOGICAL_XOR: 

1625 assert isinstance(v_lhs.value, bool) 

1626 assert isinstance(v_rhs.value, bool) 

1627 return Value(location = self.location, 

1628 value = v_lhs.value ^ v_rhs.value, 

1629 typ = self.typ) 

1630 

1631 elif self.operator == Binary_Operator.COMP_EQ: 

1632 return Value(location = self.location, 

1633 value = v_lhs.value == v_rhs.value, 

1634 typ = self.typ) 

1635 

1636 elif self.operator == Binary_Operator.COMP_NEQ: 

1637 return Value(location = self.location, 

1638 value = v_lhs.value != v_rhs.value, 

1639 typ = self.typ) 

1640 

1641 elif self.operator in (Binary_Operator.COMP_LT, 

1642 Binary_Operator.COMP_LEQ, 

1643 Binary_Operator.COMP_GT, 

1644 Binary_Operator.COMP_GEQ): 

1645 return Value( 

1646 location = self.location, 

1647 value = { 

1648 Binary_Operator.COMP_LT : lambda lhs, rhs: lhs < rhs, 

1649 Binary_Operator.COMP_LEQ : lambda lhs, rhs: lhs <= rhs, 

1650 Binary_Operator.COMP_GT : lambda lhs, rhs: lhs > rhs, 

1651 Binary_Operator.COMP_GEQ : lambda lhs, rhs: lhs >= rhs, 

1652 }[self.operator](v_lhs.value, v_rhs.value), 

1653 typ = self.typ) 

1654 

1655 elif self.operator == Binary_Operator.STRING_CONTAINS: 

1656 assert isinstance(v_lhs.value, str) 

1657 assert isinstance(v_rhs.value, str) 

1658 

1659 return Value(location = self.location, 

1660 value = v_lhs.value in v_rhs.value, 

1661 typ = self.typ) 

1662 

1663 elif self.operator == Binary_Operator.STRING_STARTSWITH: 

1664 assert isinstance(v_lhs.value, str) 

1665 assert isinstance(v_rhs.value, str) 

1666 return Value(location = self.location, 

1667 value = v_lhs.value.startswith(v_rhs.value), 

1668 typ = self.typ) 

1669 

1670 elif self.operator == Binary_Operator.STRING_ENDSWITH: 

1671 assert isinstance(v_lhs.value, str) 

1672 assert isinstance(v_rhs.value, str) 

1673 return Value(location = self.location, 

1674 value = v_lhs.value.endswith(v_rhs.value), 

1675 typ = self.typ) 

1676 

1677 elif self.operator == Binary_Operator.STRING_REGEX: 

1678 assert isinstance(v_lhs.value, str) 

1679 assert isinstance(v_rhs.value, str) 

1680 return Value(location = self.location, 

1681 value = re.match(v_rhs.value, 

1682 v_lhs.value) is not None, 

1683 typ = self.typ) 

1684 

1685 elif self.operator == Binary_Operator.ARRAY_CONTAINS: 

1686 assert isinstance(v_rhs.value, list) 

1687 

1688 return Value(location = self.location, 

1689 value = v_lhs in v_rhs.value, 

1690 typ = self.typ) 

1691 

1692 elif self.operator == Binary_Operator.PLUS: 

1693 assert isinstance(v_lhs.value, (int, str, Fraction)) 

1694 assert isinstance(v_rhs.value, (int, str, Fraction)) 

1695 return Value(location = self.location, 

1696 value = v_lhs.value + v_rhs.value, 

1697 typ = self.typ) 

1698 

1699 elif self.operator == Binary_Operator.MINUS: 

1700 assert isinstance(v_lhs.value, (int, Fraction)) 

1701 assert isinstance(v_rhs.value, (int, Fraction)) 

1702 return Value(location = self.location, 

1703 value = v_lhs.value - v_rhs.value, 

1704 typ = self.typ) 

1705 

1706 elif self.operator == Binary_Operator.TIMES: 

1707 assert isinstance(v_lhs.value, (int, Fraction)) 

1708 assert isinstance(v_rhs.value, (int, Fraction)) 

1709 return Value(location = self.location, 

1710 value = v_lhs.value * v_rhs.value, 

1711 typ = self.typ) 

1712 

1713 elif self.operator == Binary_Operator.DIVIDE: 

1714 assert isinstance(v_lhs.value, (int, Fraction)) 

1715 assert isinstance(v_rhs.value, (int, Fraction)) 

1716 

1717 if v_rhs.value == 0: 1717 ↛ 1718line 1717 didn't jump to line 1718 because the condition on line 1717 was never true

1718 mh.error(v_rhs.location, 

1719 "division by zero in %s (%s)" % 

1720 (self.to_string(), 

1721 mh.cross_file_reference(self.location))) 

1722 

1723 if isinstance(v_lhs.value, int): 

1724 return Value(location = self.location, 

1725 value = v_lhs.value // v_rhs.value, 

1726 typ = self.typ) 

1727 else: 

1728 return Value(location = self.location, 

1729 value = v_lhs.value / v_rhs.value, 

1730 typ = self.typ) 

1731 

1732 elif self.operator == Binary_Operator.REMAINDER: 

1733 assert isinstance(v_lhs.value, int) 

1734 assert isinstance(v_rhs.value, int) 

1735 

1736 if v_rhs.value == 0: 1736 ↛ 1737line 1736 didn't jump to line 1737 because the condition on line 1736 was never true

1737 mh.error(v_rhs.location, 

1738 "division by zero in %s (%s)" % 

1739 (self.to_string(), 

1740 mh.cross_file_reference(self.location))) 

1741 

1742 return Value(location = self.location, 

1743 value = math.remainder(v_lhs.value, v_rhs.value), 

1744 typ = self.typ) 

1745 

1746 elif self.operator == Binary_Operator.POWER: 

1747 assert isinstance(v_lhs.value, (int, Fraction)) 

1748 assert isinstance(v_rhs.value, int) 

1749 return Value(location = self.location, 

1750 value = v_lhs.value ** v_rhs.value, 

1751 typ = self.typ) 

1752 

1753 elif self.operator == Binary_Operator.INDEX: 

1754 assert isinstance(v_lhs.value, list) 

1755 assert isinstance(v_rhs.value, int) 

1756 

1757 if v_rhs.value < 0: 1757 ↛ 1758line 1757 didn't jump to line 1758 because the condition on line 1757 was never true

1758 mh.error(v_rhs.location, 

1759 "index cannot be less than zero in %s (%s)" % 

1760 (self.to_string(), 

1761 mh.cross_file_reference(self.location))) 

1762 elif v_lhs.typ.upper_bound is not None and \ 1762 ↛ 1764line 1762 didn't jump to line 1764 because the condition on line 1762 was never true

1763 v_rhs.value > v_lhs.typ.upper_bound: 

1764 mh.error(v_rhs.location, 

1765 "index cannot be more than %u in %s (%s)" % 

1766 (v_lhs.typ.upper_bound, 

1767 self.to_string(), 

1768 mh.cross_file_reference(self.location))) 

1769 elif v_rhs.value > len(v_lhs.value): 1769 ↛ 1770line 1769 didn't jump to line 1770 because the condition on line 1769 was never true

1770 mh.error(v_lhs.location, 

1771 "array is not big enough in %s (%s)" % 

1772 (self.to_string(), 

1773 mh.cross_file_reference(self.location))) 

1774 

1775 return Value(location = self.location, 

1776 value = v_lhs.value[v_rhs.value].value, 

1777 typ = self.typ) 

1778 

1779 else: 

1780 mh.ice_loc(self.location, 

1781 "unexpected binary operator %s" % self.operator) 

1782 

1783 def can_be_null(self): 

1784 return False 

1785 

1786 

1787class Field_Access_Expression(Expression): 

1788 """Tuple, Record, or Union field access 

1789 

1790 For example in:: 

1791 

1792 foo.bar 

1793 ^1 ^2 

1794 

1795 :attribute n_prefix: expression with tuple, record, or union type (see 1) 

1796 :type: Expression 

1797 

1798 :attribute n_field: a field to dereference (see 2) 

1799 :type: Composite_Component 

1800 

1801 :attribute is_union_access: True if the prefix is a union type 

1802 :type: bool 

1803 

1804 :attribute is_universal: True if field exists in all union members. 

1805 Only meaningful when is_union_access is True. 

1806 :type: bool 

1807 

1808 """ 

1809 def __init__(self, mh, location, n_prefix, n_field, 

1810 is_union_access=False, is_universal=True): 

1811 # lobster-trace: LRM.Union_Type_Field_Access 

1812 assert isinstance(mh, Message_Handler) 

1813 assert isinstance(n_prefix, Expression) 

1814 assert isinstance(n_field, Composite_Component) 

1815 assert isinstance(is_union_access, bool) 

1816 assert isinstance(is_universal, bool) 

1817 super().__init__(location, n_field.n_typ) 

1818 self.n_prefix = n_prefix 

1819 self.n_field = n_field 

1820 self.is_union_access = is_union_access 

1821 self.is_universal = is_universal 

1822 

1823 if not is_union_access: 

1824 self.n_prefix.ensure_type(mh, self.n_field.member_of) 

1825 

1826 def dump(self, indent=0): # pragma: no cover 

1827 # lobster-exclude: Debugging feature 

1828 self.write_indent(indent, f"Field_Access ({self.n_field.name})") 

1829 self.n_prefix.dump(indent + 1) 

1830 

1831 def to_string(self): 

1832 return self.n_prefix.to_string() + "." + self.n_field.name 

1833 

1834 def evaluate(self, mh, context, gstab): 

1835 assert isinstance(mh, Message_Handler) 

1836 assert context is None or isinstance(context, dict) 

1837 assert gstab is None or isinstance(gstab, Symbol_Table) 

1838 

1839 v_prefix = self.n_prefix.evaluate(mh, 

1840 context, 

1841 gstab).value 

1842 if v_prefix is None: 

1843 # lobster-trace: LRM.Dereference 

1844 mh.error(self.n_prefix.location, 

1845 "null dereference") 

1846 

1847 # lobster-trace: LRM.Union_Type_Partial_Field_Access 

1848 # lobster-trace: LRM.Union_Type_Partial_Field_Null 

1849 if self.n_field.name not in v_prefix: 

1850 return Value(self.location, None, None) 

1851 

1852 v_field = v_prefix[self.n_field.name] 

1853 if isinstance(v_field, Implicit_Null): 

1854 return v_field.evaluate(mh, context, gstab) 

1855 else: 

1856 return v_field 

1857 

1858 def can_be_null(self): 

1859 # A union field access on a partial field (not present in all 

1860 # member types) evaluates to null at runtime, so we must 

1861 # report True in that case. 

1862 return self.is_union_access and not self.is_universal 

1863 

1864 

1865class Range_Test(Expression): 

1866 """Range membership test 

1867 

1868 For example in:: 

1869 

1870 x in 1 .. field+1 

1871 ^lhs ^lower ^^^^^^^upper 

1872 

1873 Note that none of these are guaranteed to be literals or names; 

1874 you can have arbitrarily complex expressions here. 

1875 

1876 :attribute n_lhs: the expression to test 

1877 :type: Expression 

1878 

1879 :attribute n_lower: the lower bound 

1880 :type: Expression 

1881 

1882 :attribute n_upper: the upper bound 

1883 :type: Expression 

1884 

1885 """ 

1886 def __init__(self, mh, location, typ, n_lhs, n_lower, n_upper): 

1887 # lobster-trace: LRM.Relation 

1888 super().__init__(location, typ) 

1889 assert isinstance(mh, Message_Handler) 

1890 assert isinstance(n_lhs, Expression) 

1891 assert isinstance(n_lower, Expression) 

1892 assert isinstance(n_upper, Expression) 

1893 self.n_lhs = n_lhs 

1894 self.n_lower = n_lower 

1895 self.n_upper = n_upper 

1896 

1897 self.n_lhs.ensure_type(mh, Builtin_Numeric_Type) 

1898 self.n_lower.ensure_type(mh, self.n_lhs.typ) 

1899 self.n_upper.ensure_type(mh, self.n_lhs.typ) 

1900 

1901 def to_string(self): 

1902 return "%s in %s .. %s" % (self.n_lhs.to_string(), 

1903 self.n_lower.to_string(), 

1904 self.n_upper.to_string()) 

1905 

1906 def dump(self, indent=0): # pragma: no cover 

1907 # lobster-exclude: Debugging feature 

1908 self.write_indent(indent, "Range Test") 

1909 self.write_indent(indent + 1, f"Type: {self.typ}") 

1910 self.n_lhs.dump(indent + 1) 

1911 self.n_lower.dump(indent + 1) 

1912 self.n_upper.dump(indent + 1) 

1913 

1914 def evaluate(self, mh, context, gstab): 

1915 # lobster-trace: LRM.Null_Is_Invalid 

1916 assert isinstance(mh, Message_Handler) 

1917 assert context is None or isinstance(context, dict) 

1918 assert gstab is None or isinstance(gstab, Symbol_Table) 

1919 

1920 v_lhs = self.n_lhs.evaluate(mh, context, gstab) 

1921 if v_lhs.value is None: 1921 ↛ 1922line 1921 didn't jump to line 1922 because the condition on line 1921 was never true

1922 mh.error(v_lhs.location, 

1923 "lhs of range check %s (%s) see must not be null" % 

1924 (self.to_string(), 

1925 mh.cross_file_reference(self.location))) 

1926 

1927 v_lower = self.n_lower.evaluate(mh, context, gstab) 

1928 if v_lower.value is None: 1928 ↛ 1929line 1928 didn't jump to line 1929 because the condition on line 1928 was never true

1929 mh.error(v_lower.location, 

1930 "lower bound of range check %s (%s) must not be null" % 

1931 (self.to_string(), 

1932 mh.cross_file_reference(self.location))) 

1933 

1934 v_upper = self.n_upper.evaluate(mh, context, gstab) 

1935 if v_upper.value is None: 1935 ↛ 1936line 1935 didn't jump to line 1936 because the condition on line 1935 was never true

1936 mh.error(v_upper.location, 

1937 "upper bound of range check %s (%s) must not be null" % 

1938 (self.to_string(), 

1939 mh.cross_file_reference(self.location))) 

1940 

1941 return Value(location = self.location, 

1942 value = v_lower.value <= v_lhs.value <= v_upper.value, 

1943 typ = self.typ) 

1944 

1945 def can_be_null(self): 

1946 return False 

1947 

1948 

1949class OneOf_Expression(Expression): 

1950 """OneOf expression 

1951 

1952 For example in:: 

1953 

1954 oneof(a, b, c) 

1955 ^^^^^^^ choices 

1956 

1957 :attribute choices: a list of boolean expressions to test 

1958 :type: list[Expression] 

1959 """ 

1960 def __init__(self, mh, location, typ, choices): 

1961 # lobster-trace: LRM.Signature_OneOf 

1962 super().__init__(location, typ) 

1963 assert isinstance(typ, Builtin_Boolean) 

1964 assert isinstance(mh, Message_Handler) 

1965 assert isinstance(choices, list) 

1966 assert all(isinstance(item, Expression) 

1967 for item in choices) 

1968 self.choices = choices 

1969 

1970 for n_choice in choices: 

1971 n_choice.ensure_type(mh, Builtin_Boolean) 

1972 

1973 def to_string(self): 

1974 return "oneof(%s)" % ", ".join(n_choice.to_string() 

1975 for n_choice in self.choices) 

1976 

1977 def dump(self, indent=0): # pragma: no cover 

1978 # lobster-exclude: Debugging feature 

1979 self.write_indent(indent, "OneOf Test") 

1980 self.write_indent(indent + 1, f"Type: {self.typ}") 

1981 for n_choice in self.choices: 

1982 n_choice.dump(indent + 1) 

1983 

1984 def evaluate(self, mh, context, gstab): 

1985 # lobster-trace: LRM.OneOf_Semantics 

1986 assert isinstance(mh, Message_Handler) 

1987 assert context is None or isinstance(context, dict) 

1988 assert gstab is None or isinstance(gstab, Symbol_Table) 

1989 

1990 v_choices = [n_choice.evaluate(mh, context, gstab).value 

1991 for n_choice in self.choices] 

1992 

1993 return Value(location = self.location, 

1994 value = v_choices.count(True) == 1, 

1995 typ = self.typ) 

1996 

1997 def can_be_null(self): 

1998 return False 

1999 

2000 

2001class Action(Node): 

2002 """An if or elseif part inside a conditional expression 

2003 

2004 Each :class:`Conditional_Expression` is made up of a sequence of 

2005 Actions. For example here is a single expression with two 

2006 Actions:: 

2007 

2008 (if x == 0 then "zero" elsif x == 1 then "one" else "lots") 

2009 ^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^ 

2010 

2011 Note that the else part is not an action, it is an attribute of 

2012 the :class:`Conditional_Expression` itself. 

2013 

2014 :attribute kind: Either if or elseif 

2015 :type: str 

2016 

2017 :attribute n_cond: The boolean condition expression 

2018 :type: Expression 

2019 

2020 :attribute n_expr: The value if the condition evaluates to true 

2021 :type: Expression 

2022 

2023 """ 

2024 def __init__(self, mh, t_kind, n_condition, n_expression): 

2025 # lobster-trace: LRM.Conditional_Expression 

2026 assert isinstance(mh, Message_Handler) 

2027 assert isinstance(t_kind, Token) 

2028 assert t_kind.kind == "KEYWORD" 

2029 assert t_kind.value in ("if", "elsif") 

2030 assert isinstance(n_condition, Expression) 

2031 assert isinstance(n_expression, Expression) 

2032 super().__init__(t_kind.location) 

2033 self.kind = t_kind.value 

2034 self.n_cond = n_condition 

2035 self.n_expr = n_expression 

2036 # lobster-trace: LRM.Conditional_Expression_Types 

2037 self.n_cond.ensure_type(mh, Builtin_Boolean) 

2038 

2039 def dump(self, indent=0): # pragma: no cover 

2040 # lobster-exclude: Debugging feature 

2041 self.write_indent(indent, f"{self.kind.capitalize()} Action") 

2042 self.write_indent(indent + 1, "Condition") 

2043 self.n_cond.dump(indent + 2) 

2044 self.write_indent(indent + 1, "Value") 

2045 self.n_expr.dump(indent + 2) 

2046 

2047 def to_string(self): 

2048 return "%s %s then %s" % (self.kind, 

2049 self.n_cond.to_string(), 

2050 self.n_expr.to_string()) 

2051 

2052 

2053class Conditional_Expression(Expression): 

2054 """A conditional expression 

2055 

2056 Each :class:`Conditional_Expression` is made up of a sequence of 

2057 one or more :class:`Action`. For example here is a single 

2058 expression with two Actions:: 

2059 

2060 (if x == 0 then "zero" elsif x == 1 then "one" else "lots") 

2061 ^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^ 

2062 

2063 The else expression is part of the conditional expression itself. 

2064 

2065 A conditional expression will have at least one action (the if 

2066 action), and all other actions will be elsif actions. The else 

2067 expression is not optional and will always be present. The types 

2068 of all actions and the else expression will match. 

2069 

2070 :attribute actions: a list of Actions 

2071 :type: list[Action] 

2072 

2073 :attribute else_expr: the else expression 

2074 :type: Expression 

2075 

2076 """ 

2077 def __init__(self, location, if_action): 

2078 # lobster-trace: LRM.Conditional_Expression 

2079 assert isinstance(if_action, Action) 

2080 assert if_action.kind == "if" 

2081 super().__init__(location, if_action.n_expr.typ) 

2082 self.actions = [if_action] 

2083 self.else_expr = None 

2084 

2085 def add_elsif(self, mh, n_action): 

2086 # lobster-trace: LRM.Conditional_Expression 

2087 # lobster-trace; LRM.Conditional_Expression_Types 

2088 assert isinstance(mh, Message_Handler) 

2089 assert isinstance(n_action, Action) 

2090 assert n_action.kind == "elsif" 

2091 

2092 n_action.n_expr.ensure_type(mh, self.typ) 

2093 self.actions.append(n_action) 

2094 

2095 def set_else_part(self, mh, n_expr): 

2096 # lobster-trace: LRM.Conditional_Expression 

2097 # lobster-trace; LRM.Conditional_Expression_Types 

2098 assert isinstance(mh, Message_Handler) 

2099 assert isinstance(n_expr, Expression) 

2100 

2101 n_expr.ensure_type(mh, self.typ) 

2102 self.else_expr = n_expr 

2103 

2104 def dump(self, indent=0): # pragma: no cover 

2105 # lobster-exclude: Debugging feature 

2106 self.write_indent(indent, "Conditional expression") 

2107 for action in self.actions: 

2108 action.dump(indent + 1) 

2109 self.write_indent(indent + 1, "Else") 

2110 self.else_expr.dump(indent + 2) 

2111 

2112 def to_string(self): 

2113 rv = "(" + " ".join(action.to_string() 

2114 for action in self.actions) 

2115 rv += " else %s" % self.else_expr.to_string() 

2116 rv += ")" 

2117 return rv 

2118 

2119 def evaluate(self, mh, context, gstab): 

2120 # lobster-trace: LRM.Conditional_Expression_Else 

2121 # lobster-trace: LRM.Conditional_Expression_Evaluation 

2122 # lobster-trace: LRM.Null_Is_Invalid 

2123 assert isinstance(mh, Message_Handler) 

2124 assert context is None or isinstance(context, dict) 

2125 assert gstab is None or isinstance(gstab, Symbol_Table) 

2126 

2127 for action in self.actions: 

2128 v_cond = action.n_cond.evaluate(mh, context, gstab) 

2129 if v_cond.value is None: 2129 ↛ 2130line 2129 didn't jump to line 2130 because the condition on line 2129 was never true

2130 mh.error(v_cond.location, 

2131 "condition of %s (%s) must not be null" % 

2132 (action.to_string(), 

2133 mh.cross_file_reference(self.location))) 

2134 if v_cond.value: 

2135 return action.n_expr.evaluate(mh, context, gstab) 

2136 

2137 return self.else_expr.evaluate(mh, context, gstab) 

2138 

2139 def can_be_null(self): 

2140 if self.else_expr and self.else_expr.can_be_null(): 

2141 return True 

2142 

2143 return any(action.n_expr.can_be_null() 

2144 for action in self.actions) 

2145 

2146 

2147class Quantified_Expression(Expression): 

2148 """A quantified expression 

2149 

2150 For example:: 

2151 

2152 (forall x in array_component => x > 0) 

2153 ^4 ^1 ^2 ^^^^^3 

2154 

2155 A quantified expression introduces and binds a 

2156 :class:`Quantified_Variable` (see 1) from a specified source (see 

2157 2). When the body (see 3) is evaluated, the name of 1 is bound to 

2158 each component of the source in turn. 

2159 

2160 :attribute n_var: The quantified variable (see 1) 

2161 :type: Quantified_Variable 

2162 

2163 :attribute n_source: The array to iterate over (see 2) 

2164 :type: Name_Reference 

2165 

2166 :attribute n_expr: The body of the quantifier (see 3) 

2167 :type: Expression 

2168 

2169 :attribute universal: True means forall, false means exists (see 4) 

2170 :type: Boolean 

2171 

2172 """ 

2173 def __init__(self, mh, location, 

2174 typ, 

2175 universal, 

2176 n_variable, 

2177 n_source, 

2178 n_expr): 

2179 # lobster-trace: LRM.Quantified_Expression 

2180 # lobster-trace: LRM.Quantification_Type 

2181 super().__init__(location, typ) 

2182 assert isinstance(typ, Builtin_Boolean) 

2183 assert isinstance(universal, bool) 

2184 assert isinstance(n_variable, Quantified_Variable) 

2185 assert isinstance(n_expr, Expression) 

2186 assert isinstance(n_source, Name_Reference) 

2187 self.universal = universal 

2188 self.n_var = n_variable 

2189 self.n_expr = n_expr 

2190 self.n_source = n_source 

2191 self.n_expr.ensure_type(mh, Builtin_Boolean) 

2192 

2193 def dump(self, indent=0): # pragma: no cover 

2194 # lobster-exclude: Debugging feature 

2195 if self.universal: 

2196 self.write_indent(indent, "Universal quantified expression") 

2197 else: 

2198 self.write_indent(indent, "Existential quantified expression") 

2199 self.n_var.dump(indent + 1) 

2200 self.n_expr.dump(indent + 1) 

2201 

2202 def to_string(self): 

2203 return "(%s %s in %s => %s)" % ("forall" 

2204 if self.universal 

2205 else "exists", 

2206 self.n_var.name, 

2207 self.n_source.to_string(), 

2208 self.n_expr.to_string()) 

2209 

2210 def evaluate(self, mh, context, gstab): 

2211 # lobster-trace: LRM.Null_Is_Invalid 

2212 # lobster-trace: LRM.Universal_Quantification_Semantics 

2213 # lobster-trace: LRM.Existential_Quantification_Semantics 

2214 assert isinstance(mh, Message_Handler) 

2215 assert context is None or isinstance(context, dict) 

2216 assert gstab is None or isinstance(gstab, Symbol_Table) 

2217 

2218 if context is None: 2218 ↛ 2219line 2218 didn't jump to line 2219 because the condition on line 2218 was never true

2219 new_ctx = {} 

2220 else: 

2221 new_ctx = copy(context) 

2222 

2223 # This is going to be a bit tricky. We essentially eliminate 

2224 # the quantifier and substitute; for the sake of making better 

2225 # error messages. 

2226 assert isinstance(self.n_source.entity, Composite_Component) 

2227 array_values = context[self.n_source.entity.name] 

2228 if isinstance(array_values, Implicit_Null): 

2229 mh.error(array_values.location, 

2230 "%s in quantified expression %s (%s) " 

2231 "must not be null" % 

2232 (self.n_source.to_string(), 

2233 self.to_string(), 

2234 mh.cross_file_reference(self.location))) 

2235 else: 

2236 assert isinstance(array_values, Array_Aggregate) 

2237 

2238 rv = self.universal 

2239 loc = self.location 

2240 for binding in array_values.value: 

2241 new_ctx[self.n_var.name] = binding 

2242 result = self.n_expr.evaluate(mh, new_ctx, gstab) 

2243 assert isinstance(result.value, bool) 

2244 if self.universal and not result.value: 

2245 rv = False 

2246 loc = binding.location 

2247 break 

2248 elif not self.universal and result.value: 

2249 rv = True 

2250 loc = binding.location 

2251 break 

2252 

2253 return Value(location = loc, 

2254 value = rv, 

2255 typ = self.typ) 

2256 

2257 def can_be_null(self): 

2258 return False 

2259 

2260 

2261############################################################################## 

2262# AST Nodes (Entities) 

2263############################################################################## 

2264 

2265class Entity(Node, metaclass=ABCMeta): 

2266 """Base class for all entities. 

2267 

2268 An entity is a concrete object (with a name) for which we need to 

2269 allocate memory. Examples of entities are types and record 

2270 objects. 

2271 

2272 :attribute name: unqualified name of the entity 

2273 :type: str 

2274 

2275 """ 

2276 def __init__(self, name, location): 

2277 # lobster-trace: LRM.Described_Name_Equality 

2278 super().__init__(location) 

2279 assert isinstance(name, str) 

2280 self.name = name 

2281 

2282 

2283class Typed_Entity(Entity, metaclass=ABCMeta): 

2284 """Base class for entities with a type. 

2285 

2286 A typed entity is a concrete object (with a name and TRLC type) 

2287 for which we need to allocate memory. Examples of typed entities 

2288 are record objects and components. 

2289 

2290 :attribute n_typ: type of the entity 

2291 :type: Type 

2292 

2293 """ 

2294 def __init__(self, name, location, n_typ): 

2295 # lobster-exclude: Constructor only declares variables 

2296 super().__init__(name, location) 

2297 assert isinstance(n_typ, Type) 

2298 self.n_typ = n_typ 

2299 

2300 

2301class Quantified_Variable(Typed_Entity): 

2302 """Variable used in quantified expression. 

2303 

2304 A quantified expression declares and binds a variable, for which 

2305 we need a named entity. For example in:: 

2306 

2307 (forall x in array => x > 1) 

2308 ^ 

2309 

2310 We represent this first x as a :class:`Quantified_Variable`, the 

2311 second x will be an ordinary :class:`Name_Reference`. 

2312 

2313 :attribute typ: type of the variable (i.e. element type of the array) 

2314 :type: Type 

2315 

2316 """ 

2317 def dump(self, indent=0): # pragma: no cover 

2318 # lobster-exclude: Debugging feature 

2319 self.write_indent(indent, f"Quantified Variable {self.name}") 

2320 self.n_typ.dump(indent + 1) 

2321 

2322 

2323class Type(Entity, metaclass=ABCMeta): 

2324 """Abstract base class for all types. 

2325 

2326 """ 

2327 def perform_type_checks(self, mh, value, gstab): 

2328 assert isinstance(mh, Message_Handler) 

2329 assert isinstance(value, Expression) 

2330 assert isinstance(gstab, Symbol_Table) 

2331 return True 

2332 

2333 def get_example_value(self): 

2334 # lobster-exclude: utility method 

2335 assert False 

2336 

2337 

2338class Concrete_Type(Type, metaclass=ABCMeta): 

2339 # lobster-trace: LRM.Type_Declarations 

2340 """Abstract base class for all non-anonymous types. 

2341 

2342 :attribute n_package: package where this type was declared 

2343 :type: Package 

2344 """ 

2345 def __init__(self, name, location, n_package): 

2346 super().__init__(name, location) 

2347 assert isinstance(n_package, Package) 

2348 self.n_package = n_package 

2349 

2350 def fully_qualified_name(self): 

2351 """Return the FQN for this type (i.e. PACKAGE.NAME) 

2352 

2353 :returns: the type's full name 

2354 :rtype: str 

2355 """ 

2356 return self.n_package.name + "." + self.name 

2357 

2358 def __hash__(self): 

2359 return hash((self.n_package.name, self.name)) 

2360 

2361 def __repr__(self): 

2362 return "%s<%s>" % (self.__class__.__name__, 

2363 self.fully_qualified_name()) 

2364 

2365 

2366class Builtin_Type(Type, metaclass=ABCMeta): 

2367 # lobster-trace: LRM.Builtin_Types 

2368 """Abstract base class for all builtin types. 

2369 

2370 """ 

2371 LOCATION = Location(file_name = "<builtin>") 

2372 

2373 def __init__(self, name): 

2374 super().__init__(name, Builtin_Type.LOCATION) 

2375 

2376 def dump(self, indent=0): # pragma: no cover 

2377 self.write_indent(indent, self.__class__.__name__) 

2378 

2379 

2380class Builtin_Numeric_Type(Builtin_Type, metaclass=ABCMeta): 

2381 # lobster-trace: LRM.Builtin_Types 

2382 """Abstract base class for all builtin numeric types. 

2383 

2384 """ 

2385 def dump(self, indent=0): # pragma: no cover 

2386 self.write_indent(indent, self.__class__.__name__) 

2387 

2388 

2389class Builtin_Function(Entity): 

2390 # lobster-trace: LRM.Builtin_Functions 

2391 """Builtin functions. 

2392 

2393 These are auto-generated by the :class:`~trlc.trlc.Source_Manager`. 

2394 

2395 :attribute arity: number of parameters 

2396 :type: int 

2397 

2398 :attribute arity_at_least: when true, arity indicates a lower bound 

2399 :type: bool 

2400 

2401 """ 

2402 LOCATION = Location(file_name = "<builtin>") 

2403 

2404 def __init__(self, name, arity, arity_at_least=False): 

2405 super().__init__(name, Builtin_Function.LOCATION) 

2406 assert isinstance(arity, int) 

2407 assert isinstance(arity_at_least, bool) 

2408 assert arity >= 0 

2409 self.arity = arity 

2410 self.arity_at_least = arity_at_least 

2411 

2412 def dump(self, indent=0): # pragma: no cover 

2413 self.write_indent(indent, self.__class__.__name__ + " " + self.name) 

2414 

2415 

2416class Array_Type(Type): 

2417 """Anonymous array type. 

2418 

2419 These are declared implicitly for each record component that has 

2420 an array specifier:: 

2421 

2422 foo Integer [5 .. *] 

2423 ^ 

2424 

2425 :attribute lower_bound: minimum number of elements 

2426 :type: int 

2427 

2428 :attribute loc_lower: text location of the lower bound indicator 

2429 :type: Location 

2430 

2431 :attribute upper_bound: maximum number of elements (or None) 

2432 :type: int 

2433 

2434 :attribute loc_upper: text location of the upper bound indicator 

2435 :type: Location 

2436 

2437 :attribute element_type: type of the array elements 

2438 :type: Type 

2439 

2440 """ 

2441 def __init__(self, 

2442 location, 

2443 element_type, 

2444 loc_lower, 

2445 lower_bound, 

2446 loc_upper, 

2447 upper_bound): 

2448 # lobster-exclude: Constructor only declares variables 

2449 assert isinstance(element_type, Type) or element_type is None 

2450 assert isinstance(lower_bound, int) 

2451 assert lower_bound >= 0 

2452 assert upper_bound is None or isinstance(upper_bound, int) 

2453 assert upper_bound is None or upper_bound >= 0 

2454 assert isinstance(loc_lower, Location) 

2455 assert isinstance(loc_upper, Location) 

2456 

2457 if element_type is None: 2457 ↛ 2458line 2457 didn't jump to line 2458 because the condition on line 2457 was never true

2458 name = "universal array" 

2459 elif upper_bound is None: 

2460 if lower_bound == 0: 

2461 name = "array of %s" % element_type.name 

2462 else: 

2463 name = "array of at least %u %s" % (lower_bound, 

2464 element_type.name) 

2465 elif lower_bound == upper_bound: 

2466 name = "array of %u %s" % (lower_bound, 

2467 element_type.name) 

2468 else: 

2469 name = "array of %u to %u %s" % (lower_bound, 

2470 upper_bound, 

2471 element_type.name) 

2472 super().__init__(name, location) 

2473 self.lower_bound = lower_bound 

2474 self.loc_lower = loc_lower 

2475 self.upper_bound = upper_bound 

2476 self.loc_upper = loc_upper 

2477 self.element_type = element_type 

2478 

2479 def dump(self, indent=0): # pragma: no cover 

2480 # lobster-exclude: Debugging feature 

2481 self.write_indent(indent, "Array_Type") 

2482 self.write_indent(indent + 1, f"Lower bound: {self.lower_bound}") 

2483 if self.upper_bound is None: 

2484 self.write_indent(indent + 1, "Upper bound: *") 

2485 else: 

2486 self.write_indent(indent + 1, f"Upper bound: {self.upper_bound}") 

2487 self.write_indent(indent + 1, f"Element type: {self.element_type.name}") 

2488 

2489 def perform_type_checks(self, mh, value, gstab): 

2490 assert isinstance(mh, Message_Handler) 

2491 assert isinstance(gstab, Symbol_Table) 

2492 

2493 if isinstance(value, Array_Aggregate): 

2494 return all(self.element_type.perform_type_checks(mh, v, gstab) 

2495 for v in value.value) 

2496 else: 

2497 assert isinstance(value, Implicit_Null) 

2498 return True 

2499 

2500 def get_example_value(self): 

2501 # lobster-exclude: utility method 

2502 return "[%s]" % self.element_type.get_example_value() 

2503 

2504 

2505class Union_Type(Type): 

2506 # lobster-trace: LRM.union_type 

2507 # lobster-trace: LRM.Union_Type_Minimum_Members 

2508 # lobster-trace: LRM.Union_Type_Record_Types_Only 

2509 """Anonymous union type for record references. 

2510 

2511 These are declared implicitly when a record component specifies 

2512 multiple allowed record types using bracket syntax:: 

2513 

2514 parent [Systemrequirement, Codebeamerrequirement] 

2515 ^ 

2516 

2517 :attribute types: the allowed record types 

2518 :type: list[Record_Type] 

2519 

2520 """ 

2521 def __init__(self, location, types): 

2522 assert isinstance(types, list) 

2523 assert len(types) >= 1 

2524 assert all(isinstance(t, Record_Type) for t in types) 

2525 name = "[%s]" % ", ".join(t.name for t in types) 

2526 super().__init__(name, location) 

2527 self.types = types 

2528 self._field_map = None 

2529 

2530 def get_field_map(self): 

2531 # lobster-trace: LRM.Union_Type_Field_Access 

2532 """Compute accessible fields across all union members. 

2533 

2534 Returns a dict mapping field name to a dict with keys: 

2535 

2536 * ``component``: a representative Composite_Component 

2537 * ``n_typ``: the field type (None if conflicting) 

2538 * ``count``: how many member types have this field 

2539 * ``total``: total number of member types 

2540 * ``optional_in_any``: True if optional in at least one member 

2541 

2542 :rtype: dict[str, dict] 

2543 """ 

2544 if self._field_map is not None: 

2545 return self._field_map 

2546 

2547 field_map = {} 

2548 for record_type in self.types: 

2549 seen_in_type = set() 

2550 for comp in record_type.all_components(): 

2551 if comp.name in seen_in_type: 2551 ↛ 2552line 2551 didn't jump to line 2552 because the condition on line 2551 was never true

2552 continue 

2553 seen_in_type.add(comp.name) 

2554 if comp.name not in field_map: 

2555 field_map[comp.name] = { 

2556 "component" : comp, 

2557 "n_typ" : comp.n_typ, 

2558 "count" : 1, 

2559 "total" : len(self.types), 

2560 "optional_in_any" : comp.optional, 

2561 } 

2562 else: 

2563 info = field_map[comp.name] 

2564 info["count"] += 1 

2565 # Type identity (is) is correct here: 

2566 # non-union type objects are structural 

2567 # singletons in the symbol table, so 

2568 # identity comparison is both correct and 

2569 # cheap. 

2570 if info["n_typ"] is not comp.n_typ: 

2571 info["n_typ"] = None # type conflict 

2572 if comp.optional: 2572 ↛ 2573line 2572 didn't jump to line 2573 because the condition on line 2572 was never true

2573 info["optional_in_any"] = True 

2574 

2575 self._field_map = field_map 

2576 return self._field_map 

2577 

2578 def dump(self, indent=0): # pragma: no cover 

2579 # lobster-exclude: Debugging feature 

2580 self.write_indent(indent, "Union_Type") 

2581 for t in self.types: 

2582 self.write_indent(indent + 1, t.name) 

2583 

2584 def perform_type_checks(self, mh, value, gstab): 

2585 # Union types have no checks of their own; type validation 

2586 # happens in Record_Reference.resolve_references() via 

2587 # is_compatible(). Returning True unconditionally is 

2588 # intentional. 

2589 assert isinstance(mh, Message_Handler) 

2590 assert isinstance(value, Expression) 

2591 assert isinstance(gstab, Symbol_Table) 

2592 return True 

2593 

2594 def is_compatible(self, record_type): 

2595 """Test if the given record type is accepted by this union. 

2596 

2597 :param record_type: type to check 

2598 :type record_type: Record_Type 

2599 

2600 :returns: true if the type is or extends one of the union members 

2601 :rtype: bool 

2602 """ 

2603 assert isinstance(record_type, Record_Type) 

2604 return any(record_type.is_subclass_of(t) for t in self.types) 

2605 

2606 def get_example_value(self): 

2607 # lobster-exclude: utility method 

2608 return "%s_instance" % self.types[0].name 

2609 

2610 

2611class Builtin_Integer(Builtin_Numeric_Type): 

2612 # lobster-trace: LRM.Builtin_Types 

2613 # lobster-trace: LRM.Integer_Values 

2614 """Builtin integer type.""" 

2615 def __init__(self): 

2616 super().__init__("Integer") 

2617 

2618 def get_example_value(self): 

2619 # lobster-exclude: utility method 

2620 return "100" 

2621 

2622 

2623class Builtin_Decimal(Builtin_Numeric_Type): 

2624 # lobster-trace: LRM.Builtin_Types 

2625 # lobster-trace: LRM.Decimal_Values 

2626 """Builtin decimal type.""" 

2627 def __init__(self): 

2628 super().__init__("Decimal") 

2629 

2630 def get_example_value(self): 

2631 # lobster-exclude: utility method 

2632 return "3.14" 

2633 

2634 

2635class Builtin_Boolean(Builtin_Type): 

2636 # lobster-trace: LRM.Builtin_Types 

2637 # lobster-trace: LRM.Boolean_Values 

2638 """Builtin boolean type.""" 

2639 def __init__(self): 

2640 super().__init__("Boolean") 

2641 

2642 def get_example_value(self): 

2643 # lobster-exclude: utility method 

2644 return "true" 

2645 

2646 

2647class Builtin_String(Builtin_Type): 

2648 # lobster-trace: LRM.Builtin_Types 

2649 # lobster-trace: LRM.String_Values 

2650 """Builtin string type.""" 

2651 def __init__(self): 

2652 super().__init__("String") 

2653 

2654 def get_example_value(self): 

2655 # lobster-exclude: utility method 

2656 return "\"potato\"" 

2657 

2658 

2659class Builtin_Markup_String(Builtin_String): 

2660 # lobster-trace: LRM.Builtin_Types 

2661 # lobster-trace: LRM.Markup_String_Values 

2662 """Builtin string type that allows checked references to TRLC 

2663 objects. 

2664 """ 

2665 def __init__(self): 

2666 super().__init__() 

2667 self.name = "Markup_String" 

2668 

2669 def get_example_value(self): 

2670 # lobster-exclude: utility method 

2671 return "\"also see [[potato]]\"" 

2672 

2673 

2674class Package(Entity): 

2675 """Packages. 

2676 

2677 A package is declared when it is first encountered (in either a 

2678 rsl or trlc file). A package contains all symbols declared in it, 

2679 both types and record objects. A package is not associated with 

2680 just a single file, it can be spread over multiple files. 

2681 

2682 :attribute declared_late: indicates if this package is declared in a \ 

2683 trlc file 

2684 :type: bool 

2685 

2686 :attribute symbols: symbol table of the package 

2687 :type: Symbol_Table 

2688 

2689 """ 

2690 def __init__(self, name, location, builtin_stab, declared_late): 

2691 # lobster-exclude: Constructor only declares variables 

2692 super().__init__(name, location) 

2693 assert isinstance(builtin_stab, Symbol_Table) 

2694 assert isinstance(declared_late, bool) 

2695 self.symbols = Symbol_Table() 

2696 self.symbols.make_visible(builtin_stab) 

2697 self.declared_late = declared_late 

2698 

2699 def dump(self, indent=0): # pragma: no cover 

2700 # lobster-exclude: Debugging feature 

2701 self.write_indent(indent, f"Package {self.name}") 

2702 self.write_indent(indent + 1, f"Declared_Late: {self.declared_late}") 

2703 self.symbols.dump(indent + 1, omit_heading=True) 

2704 

2705 def __repr__(self): 

2706 return "%s<%s>" % (self.__class__.__name__, 

2707 self.name) 

2708 

2709 

2710class Composite_Type(Concrete_Type, metaclass=ABCMeta): 

2711 """Abstract base for record and tuple types, as they share some 

2712 functionality. 

2713 

2714 :attribute components: type components (including inherited if applicable) 

2715 :type: Symbol_Table[Composite_Component] 

2716 

2717 :attribute description: user-supplied description of the type or None 

2718 :type: str 

2719 

2720 :attribute checks: used-defined checks for this type (excluding \ 

2721 inherited checks) 

2722 :type: list[Check] 

2723 

2724 """ 

2725 def __init__(self, 

2726 name, 

2727 description, 

2728 location, 

2729 package, 

2730 inherited_symbols=None): 

2731 # lobster-trace: LRM.Described_Name_Description 

2732 super().__init__(name, location, package) 

2733 assert isinstance(description, str) or description is None 

2734 assert isinstance(inherited_symbols, Symbol_Table) or \ 

2735 inherited_symbols is None 

2736 

2737 self.components = Symbol_Table(inherited_symbols) 

2738 self.description = description 

2739 self.checks = [] 

2740 

2741 def add_check(self, n_check): 

2742 # lobster-trace: LRM.Check_Evaluation_Order 

2743 assert isinstance(n_check, Check) 

2744 self.checks.append(n_check) 

2745 

2746 def iter_checks(self): 

2747 # lobster-trace: LRM.Check_Evaluation_Order 

2748 yield from self.checks 

2749 

2750 def all_components(self): 

2751 # lobster-exclude: Convenience function 

2752 """Convenience function to get a list of all components. 

2753 

2754 :rtype: list[Composite_Component] 

2755 """ 

2756 return list(self.components.table.values()) 

2757 

2758 

2759class Composite_Component(Typed_Entity): 

2760 """Component in a record or tuple. 

2761 

2762 When declaring a composite type, for each component an entity is 

2763 declared:: 

2764 

2765 type|tuple T { 

2766 foo "blah" optional Boolean 

2767 ^1 ^2 ^3 ^4 

2768 

2769 :attribute description: optional text (see 2) for this component, or None 

2770 :type: str 

2771 

2772 :attribute member_of: a link back to the containing record or tuple; \ 

2773 for inherited fields this refers back to the original base record type 

2774 :type: Composite_Type 

2775 

2776 :attribute optional: indicates if the component can be null or not (see 3) 

2777 :type: bool 

2778 

2779 """ 

2780 

2781 def __init__(self, 

2782 name, 

2783 description, 

2784 location, 

2785 member_of, 

2786 n_typ, 

2787 optional): 

2788 # lobster-trace: LRM.Described_Name_Description 

2789 super().__init__(name, location, n_typ) 

2790 assert isinstance(description, str) or description is None 

2791 assert isinstance(member_of, Composite_Type) 

2792 assert isinstance(optional, bool) 

2793 self.description = description 

2794 self.member_of = member_of 

2795 self.optional = optional 

2796 

2797 def dump(self, indent=0): # pragma: no cover 

2798 # lobster-exclude: Debugging feature 

2799 self.write_indent(indent, f"Composite_Component {self.name}") 

2800 if self.description: 

2801 self.write_indent(indent + 1, f"Description: {self.description}") 

2802 self.write_indent(indent + 1, f"Optional: {self.optional}") 

2803 self.write_indent(indent + 1, f"Type: {self.n_typ.name}") 

2804 

2805 def __repr__(self): 

2806 return "%s<%s>" % (self.__class__.__name__, 

2807 self.member_of.fully_qualified_name() + "." + 

2808 self.name) 

2809 

2810 

2811class Record_Type(Composite_Type): 

2812 """A user-defined record type. 

2813 

2814 In this example:: 

2815 

2816 type T "optional description of T" extends Root_T { 

2817 ^1 ^2 ^3 

2818 

2819 Note that (1) is part of the :class:`Entity` base, and (2) is part 

2820 of the :class:`Composite_Type` base. 

2821 

2822 :attribute parent: root type or None, indicated by (3) above 

2823 :type: Record_Type 

2824 

2825 :attribute frozen: mapping of frozen components 

2826 :type: dict[str, Expression] 

2827 

2828 :attribute is_final: type is final (i.e. no new components may be declared) 

2829 :type: bool 

2830 

2831 :attribute is_abstract: type is abstract 

2832 :type: bool 

2833 

2834 """ 

2835 def __init__(self, 

2836 name, 

2837 description, 

2838 location, 

2839 package, 

2840 n_parent, 

2841 is_abstract): 

2842 # lobster-exclude: Constructor only declares variables 

2843 assert isinstance(n_parent, Record_Type) or n_parent is None 

2844 assert isinstance(is_abstract, bool) 

2845 super().__init__(name, 

2846 description, 

2847 location, 

2848 package, 

2849 n_parent.components if n_parent else None) 

2850 self.parent = n_parent 

2851 self.frozen = {} 

2852 self.is_final = (n_parent.is_final if n_parent else False) 

2853 self.is_abstract = is_abstract 

2854 

2855 def iter_checks(self): 

2856 # lobster-trace: LRM.Check_Evaluation_Order 

2857 # lobster-trace: LRM.Check_Evaluation_Order_For_Extensions 

2858 if self.parent: 

2859 yield from self.parent.iter_checks() 

2860 yield from self.checks 

2861 

2862 def dump(self, indent=0): # pragma: no cover 

2863 # lobster-exclude: Debugging feature 

2864 self.write_indent(indent, f"Record_Type {self.name}") 

2865 if self.description: 

2866 self.write_indent(indent + 1, f"Description: {self.description}") 

2867 if self.parent: 

2868 self.write_indent(indent + 1, f"Parent: {self.parent.name}") 

2869 self.components.dump(indent + 1, omit_heading=True) 

2870 if self.checks: 

2871 self.write_indent(indent + 1, "Checks") 

2872 for n_check in self.checks: 

2873 n_check.dump(indent + 2) 

2874 else: 

2875 self.write_indent(indent + 1, "Checks: None") 

2876 

2877 def all_components(self): 

2878 """Convenience function to get a list of all components. 

2879 

2880 :rtype: list[Composite_Component] 

2881 """ 

2882 if self.parent: 

2883 return self.parent.all_components() + \ 

2884 list(self.components.table.values()) 

2885 else: 

2886 return list(self.components.table.values()) 

2887 

2888 def is_subclass_of(self, record_type): 

2889 """ Checks if this record type is or inherits from the given type 

2890 

2891 :param record_type: check if are or extend this type 

2892 :type record_type: Record_Type 

2893 

2894 :returns: true if we are or extend the given type 

2895 :rtype: Boolean 

2896 """ 

2897 assert isinstance(record_type, Record_Type) 

2898 

2899 ptr = self 

2900 while ptr: 

2901 if ptr is record_type: 

2902 return True 

2903 else: 

2904 ptr = ptr.parent 

2905 return False 

2906 

2907 def is_frozen(self, n_component): 

2908 """Test if the given component is frozen. 

2909 

2910 :param n_component: a composite component of this record type \ 

2911 (or any of its parents) 

2912 :type n_component: Composite_Component 

2913 

2914 :rtype: bool 

2915 """ 

2916 assert isinstance(n_component, Composite_Component) 

2917 if n_component.name in self.frozen: 

2918 return True 

2919 elif self.parent: 

2920 return self.parent.is_frozen(n_component) 

2921 else: 

2922 return False 

2923 

2924 def get_freezing_expression(self, n_component): 

2925 """Retrieve the frozen value for a frozen component 

2926 

2927 It is an internal compiler error to call this method with a 

2928 component that his not frozen. 

2929 

2930 :param n_component: a frozen component of this record type \ 

2931 (or any of its parents) 

2932 :type n_component: Composite_Component 

2933 

2934 :rtype: Expression 

2935 

2936 """ 

2937 assert isinstance(n_component, Composite_Component) 

2938 if n_component.name in self.frozen: 2938 ↛ 2940line 2938 didn't jump to line 2940 because the condition on line 2938 was always true

2939 return self.frozen[n_component.name] 

2940 elif self.parent: 

2941 return self.parent.get_freezing_expression(n_component) 

2942 else: 

2943 assert False 

2944 

2945 def get_example_value(self): 

2946 # lobster-exclude: utility method 

2947 return "%s_instance" % self.name 

2948 

2949 

2950class Tuple_Type(Composite_Type): 

2951 """A user-defined tuple type. 

2952 

2953 In this example:: 

2954 

2955 tuple T "optional description of T" { 

2956 ^1 ^2 

2957 

2958 Note that (1) is part of the :class:`Entity` base, and (2) is part 

2959 of the :class:`Composite_Type` base. 

2960 

2961 :attribute separators: list of syntactic separators. 

2962 :type: list[Separator] 

2963 

2964 Note the list of separators will either be empty, or there will be 

2965 precisely one less separator than components. 

2966 

2967 """ 

2968 def __init__(self, name, description, location, package): 

2969 # lobster-trace: LRM.Tuple_Declaration 

2970 super().__init__(name, 

2971 description, 

2972 location, 

2973 package) 

2974 self.separators = [] 

2975 

2976 def add_separator(self, n_separator): 

2977 # lobster-exclude: utility method 

2978 assert isinstance(n_separator, Separator) 

2979 assert len(self.separators) + 1 == len(self.components.table) 

2980 self.separators.append(n_separator) 

2981 

2982 def iter_separators(self): 

2983 """Iterate over all separators""" 

2984 # lobster-exclude: utility method 

2985 yield from self.separators 

2986 

2987 def iter_sequence(self): 

2988 """Iterate over all components and separators in syntactic order""" 

2989 # lobster-exclude: utility method 

2990 if self.separators: 

2991 for i, n_component in enumerate(self.components.table.values()): 

2992 yield n_component 

2993 if i < len(self.separators): 

2994 yield self.separators[i] 

2995 else: 

2996 yield from self.components.table.values() 

2997 

2998 def has_separators(self): 

2999 """Returns true if a tuple type requires separators""" 

3000 # lobster-exclude: utility method 

3001 return bool(self.separators) 

3002 

3003 def dump(self, indent=0): # pragma: no cover 

3004 # lobster-exclude: Debugging feature 

3005 self.write_indent(indent, f"Tuple_Type {self.name}") 

3006 if self.description: 

3007 self.write_indent(indent + 1, f"Description: {self.description}") 

3008 self.write_indent(indent + 1, "Fields") 

3009 for n_item in self.iter_sequence(): 

3010 n_item.dump(indent + 2) 

3011 if self.checks: 

3012 self.write_indent(indent + 1, "Checks") 

3013 for n_check in self.checks: 

3014 n_check.dump(indent + 2) 

3015 else: 

3016 self.write_indent(indent + 1, "Checks: None") 

3017 

3018 def perform_type_checks(self, mh, value, gstab): 

3019 # lobster-trace: LRM.Check_Evaluation_Order 

3020 assert isinstance(mh, Message_Handler) 

3021 assert isinstance(gstab, Symbol_Table) 

3022 

3023 if isinstance(value, Tuple_Aggregate): 3023 ↛ 3030line 3023 didn't jump to line 3030 because the condition on line 3023 was always true

3024 ok = True 

3025 for check in self.iter_checks(): 

3026 if not check.perform(mh, value, gstab): 3026 ↛ 3027line 3026 didn't jump to line 3027 because the condition on line 3026 was never true

3027 ok = False 

3028 return ok 

3029 else: 

3030 assert isinstance(value, Implicit_Null) 

3031 return True 

3032 

3033 def get_example_value(self): 

3034 # lobster-exclude: utility method 

3035 parts = [] 

3036 for n_item in self.iter_sequence(): 

3037 if isinstance(n_item, Composite_Component): 

3038 parts.append(n_item.n_typ.get_example_value()) 

3039 else: 

3040 parts.append(n_item.to_string()) 

3041 if self.has_separators(): 

3042 return " ".join(parts) 

3043 else: 

3044 return "(%s)" % ", ".join(parts) 

3045 

3046 

3047class Separator(Node): 

3048 # lobster-trace: LRM.Tuple_Declaration 

3049 """User-defined syntactic separator 

3050 

3051 For example:: 

3052 

3053 separator x 

3054 ^1 

3055 

3056 :attribute token: token used to separate fields of the tuple 

3057 :type: Token 

3058 """ 

3059 def __init__(self, token): 

3060 super().__init__(token.location) 

3061 assert isinstance(token, Token) and token.kind in ("IDENTIFIER", 

3062 "AT", 

3063 "COLON", 

3064 "SEMICOLON") 

3065 self.token = token 

3066 

3067 def to_string(self): 

3068 return { 

3069 "AT" : "@", 

3070 "COLON" : ":", 

3071 "SEMICOLON" : ";" 

3072 }.get(self.token.kind, self.token.value) 

3073 

3074 def dump(self, indent=0): # pragma: no cover 

3075 self.write_indent(indent, f"Separator {self.token.value}") 

3076 

3077 

3078class Enumeration_Type(Concrete_Type): 

3079 """User-defined enumeration types. 

3080 

3081 For example:: 

3082 

3083 enum T "potato" { 

3084 ^1 ^2 

3085 

3086 :attribute description: user supplied optional description, or None 

3087 :type: str 

3088 

3089 :attribute literals: the literals in this enumeration 

3090 :type: Symbol_Table[Enumeration_Literal_Spec] 

3091 

3092 """ 

3093 def __init__(self, name, description, location, package): 

3094 # lobster-trace: LRM.Described_Name_Description 

3095 super().__init__(name, location, package) 

3096 assert isinstance(description, str) or description is None 

3097 self.literals = Symbol_Table() 

3098 self.description = description 

3099 

3100 def dump(self, indent=0): # pragma: no cover 

3101 # lobster-exclude: Debugging feature 

3102 self.write_indent(indent, f"Enumeration_Type {self.name}") 

3103 if self.description: 

3104 self.write_indent(indent + 1, f"Description: {self.description}") 

3105 self.literals.dump(indent + 1, omit_heading=True) 

3106 

3107 def get_example_value(self): 

3108 # lobster-exclude: utility method 

3109 options = list(self.literals.values()) 

3110 if options: 

3111 choice = len(options) // 2 

3112 return self.name + "." + choice.name 

3113 else: 

3114 return "ERROR" 

3115 

3116 

3117class Enumeration_Literal_Spec(Typed_Entity): 

3118 """Declared literal in an enumeration declaration. 

3119 

3120 Note that for literals mentioned later in record object 

3121 declarations, we use :class:`Enumeration_Literal`. Literal specs 

3122 are used here:: 

3123 

3124 enum ASIL { 

3125 QM "not safety related" 

3126 ^1 ^2 

3127 

3128 :attribute description: the optional user-supplied description, or None 

3129 :type: str 

3130 

3131 """ 

3132 def __init__(self, name, description, location, enum): 

3133 # lobster-trace: LRM.Described_Name_Description 

3134 super().__init__(name, location, enum) 

3135 assert isinstance(description, str) or description is None 

3136 assert isinstance(enum, Enumeration_Type) 

3137 self.description = description 

3138 

3139 def dump(self, indent=0): # pragma: no cover 

3140 # lobster-exclude: Debugging feature 

3141 self.write_indent(indent, f"Enumeration_Literal_Spec {self.name}") 

3142 if self.description: 

3143 self.write_indent(indent + 1, f"Description: {self.description}") 

3144 

3145 

3146class Record_Object(Typed_Entity): 

3147 """A declared instance of a record type. 

3148 

3149 This is going to be the bulk of all entities created by TRLC:: 

3150 

3151 section "Potato" { 

3152 ^5 

3153 Requirement PotatoReq { 

3154 ^1 ^2 

3155 component1 = 42 

3156 ^3 ^4 

3157 

3158 Note that the name (see 2) and type (see 1) of the object is 

3159 provided by the name attribute of the :class:`Typed_Entity` base 

3160 class. 

3161 

3162 :attribute field: the specific values for all components (see 3 and 4) 

3163 :type: dict[str, Expression] 

3164 

3165 :attribute section: None or the section this record is contained in (see 5) 

3166 :type: Section 

3167 

3168 :attribute n_package: The package in which this record is declared in 

3169 :type: Section 

3170 

3171 The actual type of expressions in the field attribute are limited 

3172 to: 

3173 

3174 * :class:`Literal` 

3175 * :class:`Unary_Expression` 

3176 * :class:`Array_Aggregate` 

3177 * :class:`Tuple_Aggregate` 

3178 * :class:`Record_Reference` 

3179 * :class:`Implicit_Null` 

3180 

3181 """ 

3182 def __init__(self, name, location, n_typ, section, n_package): 

3183 # lobster-trace: LRM.Section_Declaration 

3184 # lobster-trace: LRM.Unspecified_Optional_Components 

3185 # lobster-trace: LRM.Record_Object_Declaration 

3186 

3187 assert isinstance(n_typ, Record_Type) 

3188 assert isinstance(section, list) or section is None 

3189 assert isinstance(n_package, Package) 

3190 super().__init__(name, location, n_typ) 

3191 self.field = { 

3192 comp.name: Implicit_Null(self, comp) 

3193 for comp in self.n_typ.all_components() 

3194 } 

3195 self.section = section 

3196 self.n_package = n_package 

3197 

3198 def fully_qualified_name(self): 

3199 """Return the FQN for this type (i.e. PACKAGE.NAME) 

3200 

3201 :returns: the object's full name 

3202 :rtype: str 

3203 """ 

3204 return self.n_package.name + "." + self.name 

3205 

3206 def to_python_dict(self): 

3207 """Return an evaluated and simplified object for Python. 

3208 

3209 For example it might provide:: 

3210 

3211 {"foo" : [1, 2, 3], 

3212 "bar" : None, 

3213 "baz" : "value"} 

3214 

3215 This is a function especially designed for the Python API. The 

3216 name of the object itself is not in this returned dictionary. 

3217 

3218 """ 

3219 return {name: value.to_python_object() 

3220 for name, value in self.field.items()} 

3221 

3222 def is_component_implicit_null(self, component) -> bool: 

3223 return not isinstance(self.field[component.name], Implicit_Null) 

3224 

3225 def assign(self, component, value): 

3226 assert isinstance(component, Composite_Component) 

3227 assert isinstance(value, (Literal, 

3228 Array_Aggregate, 

3229 Tuple_Aggregate, 

3230 Record_Reference, 

3231 Implicit_Null, 

3232 Unary_Expression)), \ 

3233 "value is %s" % value.__class__.__name__ 

3234 if self.is_component_implicit_null(component): 3234 ↛ 3235line 3234 didn't jump to line 3235 because the condition on line 3234 was never true

3235 raise KeyError(f"Component {component.name} already \ 

3236 assigned to {self.n_typ.name} {self.name}!") 

3237 self.field[component.name] = value 

3238 

3239 def dump(self, indent=0): # pragma: no cover 

3240 # lobster-exclude: Debugging feature 

3241 self.write_indent(indent, f"Record_Object {self.name}") 

3242 self.write_indent(indent + 1, f"Type: {self.n_typ.name}") 

3243 for key, value in self.field.items(): 

3244 self.write_indent(indent + 1, f"Field {key}") 

3245 value.dump(indent + 2) 

3246 if self.section: 

3247 self.section[-1].dump(indent + 1) 

3248 

3249 def resolve_references(self, mh): 

3250 assert isinstance(mh, Message_Handler) 

3251 for val in self.field.values(): 

3252 val.resolve_references(mh) 

3253 

3254 def perform_checks(self, mh, gstab): 

3255 # lobster-trace: LRM.Check_Evaluation_Order 

3256 # lobster-trace: LRM.Evaluation_Of_Checks 

3257 assert isinstance(mh, Message_Handler) 

3258 assert isinstance(gstab, Symbol_Table) 

3259 

3260 ok = True 

3261 

3262 # First evaluate all tuple checks 

3263 for n_comp in self.n_typ.all_components(): 

3264 if not n_comp.n_typ.perform_type_checks(mh, 3264 ↛ 3267line 3264 didn't jump to line 3267 because the condition on line 3264 was never true

3265 self.field[n_comp.name], 

3266 gstab): 

3267 ok = False 

3268 

3269 # TODO: Is there a bug here (a check relies on a tuple check)? 

3270 

3271 # Then evaluate all record checks 

3272 for check in self.n_typ.iter_checks(): 

3273 # Prints messages, if applicable. Raises exception on 

3274 # fatal checks, which causes this to abort. 

3275 if not check.perform(mh, self, gstab): 

3276 ok = False 

3277 

3278 return ok 

3279 

3280 def __repr__(self): 

3281 return "%s<%s>" % (self.__class__.__name__, 

3282 self.n_package.name + "." + 

3283 self.n_typ.name + "." + 

3284 self.name) 

3285 

3286 

3287class Section(Entity): 

3288 # lobster-trace: LRM.Section_Declaration 

3289 """A section for readability 

3290 

3291 This represents a section construct in TRLC files to group record 

3292 objects together:: 

3293 

3294 section "Foo" { 

3295 ^^^^^ parent section 

3296 section "Bar" { 

3297 ^^^^^ section 

3298 

3299 :attribute parent: the parent section or None 

3300 :type: Section 

3301 

3302 """ 

3303 def __init__(self, name, location, parent): 

3304 super().__init__(name, location) 

3305 assert isinstance(parent, Section) or parent is None 

3306 self.parent = parent 

3307 

3308 def dump(self, indent=0): # pragma: no cover 

3309 self.write_indent(indent, f"Section {self.name}") 

3310 if self.parent is None: 

3311 self.write_indent(indent + 1, "Parent: None") 

3312 else: 

3313 self.write_indent(indent + 1, f"Parent: {self.parent.name}") 

3314 

3315 

3316############################################################################## 

3317# Symbol Table & Scopes 

3318############################################################################## 

3319 

3320class Symbol_Table: 

3321 """ Symbol table mapping names to entities 

3322 """ 

3323 def __init__(self, parent=None): 

3324 # lobster-exclude: Constructor only declares variables 

3325 assert isinstance(parent, Symbol_Table) or parent is None 

3326 self.parent = parent 

3327 self.imported = [] 

3328 self.table = OrderedDict() 

3329 self.trlc_files = [] 

3330 self.section_names = [] 

3331 

3332 @staticmethod 

3333 def simplified_name(name): 

3334 # lobster-trace: LRM.Sufficiently_Distinct 

3335 assert isinstance(name, str) 

3336 return name.lower().replace("_", "") 

3337 

3338 def all_names(self): 

3339 # lobster-exclude: API for users 

3340 """ All names in the symbol table 

3341 

3342 :rtype: set[str] 

3343 """ 

3344 rv = set(item.name for item in self.table.values()) 

3345 if self.parent: 

3346 rv |= self.parent.all_names() 

3347 return rv 

3348 

3349 def iter_record_objects_by_section(self): 

3350 """API for users 

3351 

3352 Retriving information about the section hierarchy for record objects 

3353 Inputs: folder with trlc files where trlc files have sections, 

3354 sub sections and record objects 

3355 Output: Information about sections and level of sections, 

3356 record objects and levels of record object 

3357 """ 

3358 for record_object in self.iter_record_objects(): 

3359 location = record_object.location.file_name 

3360 if location not in self.trlc_files: 3360 ↛ 3363line 3360 didn't jump to line 3363 because the condition on line 3360 was always true

3361 self.trlc_files.append(location) 

3362 yield location 

3363 if record_object.section: 

3364 object_level = len(record_object.section) - 1 

3365 for level, section in enumerate(record_object.section): 

3366 if section not in self.section_names: 3366 ↛ 3365line 3366 didn't jump to line 3365 because the condition on line 3366 was always true

3367 self.section_names.append(section) 

3368 yield section.name, level 

3369 yield record_object, object_level 

3370 else: 

3371 object_level = 0 

3372 yield record_object, object_level 

3373 

3374 def iter_record_objects(self): 

3375 # lobster-exclude: API for users 

3376 """ Iterate over all record objects 

3377 

3378 :rtype: iterable[Record_Object] 

3379 """ 

3380 for item in self.table.values(): 

3381 if isinstance(item, Package): 

3382 yield from item.symbols.iter_record_objects() 

3383 

3384 elif isinstance(item, Record_Object): 

3385 yield item 

3386 

3387 def values(self, subtype=None): 

3388 # lobster-exclude: API for users 

3389 assert subtype is None or isinstance(subtype, type) 

3390 if self.parent: 

3391 yield from self.parent.values(subtype) 

3392 for name in sorted(self.table): 

3393 if subtype is None or isinstance(self.table[name], subtype): 

3394 yield self.table[name] 

3395 

3396 def make_visible(self, stab): 

3397 assert isinstance(stab, Symbol_Table) 

3398 self.imported.append(stab) 

3399 

3400 def register(self, mh, entity): 

3401 # lobster-trace: LRM.Duplicate_Types 

3402 # lobster-trace: LRM.Unique_Enumeration_Literals 

3403 # lobster-trace: LRM.Tuple_Unique_Field_Names 

3404 # lobster-trace: LRM.Sufficiently_Distinct 

3405 # lobster-trace: LRM.Unique_Object_Names 

3406 

3407 assert isinstance(mh, Message_Handler) 

3408 assert isinstance(entity, Entity) 

3409 

3410 simple_name = self.simplified_name(entity.name) 

3411 

3412 if self.contains_raw(simple_name): 

3413 pdef = self.lookup_direct(mh, entity.name, entity.location, 

3414 simplified=True) 

3415 if pdef.name == entity.name: 

3416 mh.error(entity.location, 

3417 "duplicate definition, previous definition at %s" % 

3418 mh.cross_file_reference(pdef.location)) 

3419 else: 

3420 mh.error(entity.location, 

3421 "%s is too similar to %s, declared at %s" % 

3422 (entity.name, 

3423 pdef.name, 

3424 mh.cross_file_reference(pdef.location))) 

3425 

3426 else: 

3427 self.table[simple_name] = entity 

3428 

3429 def __contains__(self, name): 

3430 # lobster-trace: LRM.Described_Name_Equality 

3431 return self.contains(name) 

3432 

3433 def contains_raw(self, simple_name, precise_name=None): 

3434 # lobster-trace: LRM.Described_Name_Equality 

3435 # lobster-trace: LRM.Sufficiently_Distinct 

3436 # 

3437 # Internal function to test if the simplified name is in the 

3438 # table. 

3439 assert isinstance(simple_name, str) 

3440 assert isinstance(precise_name, str) or precise_name is None 

3441 

3442 if simple_name in self.table: 

3443 # No need to continue searching since registering a 

3444 # clashing name would have been stopped 

3445 return precise_name is None or \ 

3446 self.table[simple_name].name == precise_name 

3447 

3448 elif self.parent: 

3449 return self.parent.contains_raw(simple_name, precise_name) 

3450 

3451 for stab in self.imported: 

3452 if stab.contains_raw(simple_name, precise_name): 

3453 return True 

3454 

3455 return False 

3456 

3457 def contains(self, name): 

3458 # lobster-trace: LRM.Described_Name_Equality 

3459 """ Tests if the given name is in the table 

3460 

3461 :param name: the name to test 

3462 :type name: str 

3463 

3464 :rtype: bool 

3465 """ 

3466 assert isinstance(name, str) 

3467 return self.contains_raw(self.simplified_name(name), name) 

3468 

3469 def lookup_assuming(self, mh, name, required_subclass=None): 

3470 # lobster-trace: LRM.Described_Name_Equality 

3471 # lobster-trace: LRM.Sufficiently_Distinct 

3472 """Retrieve an object from the table assuming its there 

3473 

3474 This is intended for the API specifically where you want to 

3475 e.g. find some used-defined types you know are there. 

3476 

3477 :param mh: The message handler to use 

3478 :type mh: Message_Handler 

3479 

3480 :param name: The name to search for 

3481 :type name: str 

3482 

3483 :param required_subclass: If set, creates an error if the object \ 

3484 is not an instance of the given class 

3485 :type required_subclass: type 

3486 

3487 :raise TRLC_Error: if the object is not of the required subclass 

3488 :returns: the specified entity (or None if it does not exist) 

3489 :rtype: Entity 

3490 

3491 """ 

3492 assert isinstance(mh, Message_Handler) 

3493 assert isinstance(name, str) 

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

3495 

3496 simple_name = self.simplified_name(name) 

3497 

3498 ptr = self 

3499 for ptr in [self] + self.imported: 3499 ↛ 3517line 3499 didn't jump to line 3517 because the loop on line 3499 didn't complete

3500 while ptr: 3500 ↛ 3499line 3500 didn't jump to line 3499 because the condition on line 3500 was always true

3501 if simple_name in ptr.table: 3501 ↛ 3515line 3501 didn't jump to line 3515 because the condition on line 3501 was always true

3502 rv = ptr.table[simple_name] 

3503 if rv.name != name: 3503 ↛ 3504line 3503 didn't jump to line 3504 because the condition on line 3503 was never true

3504 return None 

3505 

3506 if required_subclass is not None and \ 3506 ↛ 3508line 3506 didn't jump to line 3508 because the condition on line 3506 was never true

3507 not isinstance(rv, required_subclass): 

3508 mh.error(rv.location, 

3509 "%s %s is not a %s" % 

3510 (rv.__class__.__name__, 

3511 name, 

3512 required_subclass.__name__)) 

3513 return rv 

3514 else: 

3515 ptr = ptr.parent 

3516 

3517 return None 

3518 

3519 def lookup_direct(self, 

3520 mh, 

3521 name, 

3522 error_location, 

3523 required_subclass=None, 

3524 simplified=False): 

3525 # lobster-trace: LRM.Described_Name_Equality 

3526 # lobster-trace: LRM.Sufficiently_Distinct 

3527 # lobster-trace: LRM.Valid_Base_Names 

3528 # lobster-trace: LRM.Valid_Access_Prefixes 

3529 # lobster-trace: LRM.Valid_Function_Prefixes 

3530 """Retrieve an object from the table 

3531 

3532 For example:: 

3533 

3534 pkg = stab.lookup_direct(mh, 

3535 "potato", 

3536 Location("foobar.txt", 42), 

3537 Package) 

3538 

3539 This would search for an object named ``potato``. If it is 

3540 found, and it is a package, it is returned. If it is not a 

3541 Package, then the following error is issued:: 

3542 

3543 foobar.txt:42: error: Enumeration_Type potato is not a Package 

3544 

3545 If it is not found at all, then the following error is issued:: 

3546 

3547 foobar.txt:42: error: unknown symbol potato 

3548 

3549 :param mh: The message handler to use 

3550 :type mh: Message_Handler 

3551 

3552 :param name: The name to search for 

3553 :type name: str 

3554 

3555 :param error_location: Where to create the error if the name is \ 

3556 not found 

3557 :type error_location: Location 

3558 

3559 :param required_subclass: If set, creates an error if the object \ 

3560 is not an instance of the given class 

3561 :type required_subclass: type 

3562 

3563 :param simplified: If set, look up the given simplified name instead \ 

3564 of the actual name 

3565 :type simplified: bool 

3566 

3567 :raise TRLC_Error: if the name is not in the table 

3568 :raise TRLC_Error: if the object is not of the required subclass 

3569 :returns: the specified entity 

3570 :rtype: Entity 

3571 

3572 """ 

3573 assert isinstance(mh, Message_Handler) 

3574 assert isinstance(name, str) 

3575 assert isinstance(error_location, Location) 

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

3577 assert isinstance(simplified, bool) 

3578 

3579 simple_name = self.simplified_name(name) 

3580 ptr = self 

3581 options = [] 

3582 

3583 for ptr in [self] + self.imported: 

3584 while ptr: 

3585 if simple_name in ptr.table: 

3586 rv = ptr.table[simple_name] 

3587 if not simplified and rv.name != name: 3587 ↛ 3588line 3587 didn't jump to line 3588 because the condition on line 3587 was never true

3588 mh.error(error_location, 

3589 "unknown symbol %s, did you mean %s?" % 

3590 (name, 

3591 rv.name)) 

3592 

3593 if required_subclass is not None and \ 

3594 not isinstance(rv, required_subclass): 

3595 mh.error(error_location, 

3596 "%s %s is not a %s" % 

3597 (rv.__class__.__name__, 

3598 name, 

3599 required_subclass.__name__)) 

3600 return rv 

3601 else: 

3602 options += list(item.name 

3603 for item in ptr.table.values()) 

3604 ptr = ptr.parent 

3605 

3606 matches = get_close_matches( 

3607 word = name, 

3608 possibilities = options, 

3609 n = 1) 

3610 

3611 if matches: 

3612 mh.error(error_location, 

3613 "unknown symbol %s, did you mean %s?" % 

3614 (name, 

3615 matches[0])) 

3616 else: 

3617 mh.error(error_location, 

3618 "unknown symbol %s" % name) 

3619 

3620 def lookup(self, mh, referencing_token, required_subclass=None): 

3621 # lobster-trace: LRM.Described_Name_Equality 

3622 assert isinstance(mh, Message_Handler) 

3623 assert isinstance(referencing_token, Token) 

3624 assert referencing_token.kind in ("IDENTIFIER", "BUILTIN") 

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

3626 

3627 return self.lookup_direct( 

3628 mh = mh, 

3629 name = referencing_token.value, 

3630 error_location = referencing_token.location, 

3631 required_subclass = required_subclass) 

3632 

3633 def write_indent(self, indent, message): # pragma: no cover 

3634 # lobster-exclude: Debugging feature 

3635 assert isinstance(indent, int) 

3636 assert indent >= 0 

3637 assert isinstance(message, str) 

3638 print(" " * (3 * indent) + message) 

3639 

3640 def dump(self, indent=0, omit_heading=False): # pragma: no cover 

3641 # lobster-exclude: Debugging feature 

3642 if omit_heading: 

3643 new_indent = indent 

3644 else: 

3645 self.write_indent(indent, "Symbol_Table") 

3646 new_indent = indent + 1 

3647 ptr = self 

3648 while ptr: 

3649 for name in ptr.table: 

3650 ptr.table[name].dump(new_indent) 

3651 ptr = ptr.parent 

3652 

3653 @classmethod 

3654 def create_global_table(cls, mh): 

3655 # lobster-trace: LRM.Builtin_Types 

3656 # lobster-trace: LRM.Builtin_Functions 

3657 # lobster-trace: LRM.Builtin_Type_Conversion_Functions 

3658 # lobster-trace: LRM.Signature_Len 

3659 # lobster-trace: LRM.Signature_String_End_Functions 

3660 # lobster-trace: LRM.Signature_Matches 

3661 

3662 stab = Symbol_Table() 

3663 stab.register(mh, Builtin_Integer()) 

3664 stab.register(mh, Builtin_Decimal()) 

3665 stab.register(mh, Builtin_Boolean()) 

3666 stab.register(mh, Builtin_String()) 

3667 stab.register(mh, Builtin_Markup_String()) 

3668 stab.register(mh, 

3669 Builtin_Function("len", 1)) 

3670 stab.register(mh, 

3671 Builtin_Function("startswith", 2)) 

3672 stab.register(mh, 

3673 Builtin_Function("endswith", 2)) 

3674 stab.register(mh, 

3675 Builtin_Function("matches", 2)) 

3676 stab.register(mh, 

3677 Builtin_Function("oneof", 1, arity_at_least=True)) 

3678 

3679 return stab 

3680 

3681 

3682class Scope: 

3683 def __init__(self): 

3684 # lobster-exclude: Constructor only declares variables 

3685 self.scope = [] 

3686 

3687 def push(self, stab): 

3688 assert isinstance(stab, Symbol_Table) 

3689 self.scope.append(stab) 

3690 

3691 def pop(self): 

3692 self.scope.pop() 

3693 

3694 def contains(self, name): 

3695 assert isinstance(name, str) 

3696 

3697 for stab in reversed(self.scope): 

3698 if stab.contains(name): 

3699 return True 

3700 return False 

3701 

3702 def lookup(self, mh, referencing_token, required_subclass=None): 

3703 assert len(self.scope) >= 1 

3704 assert isinstance(mh, Message_Handler) 

3705 assert isinstance(referencing_token, Token) 

3706 assert referencing_token.kind in ("IDENTIFIER", "BUILTIN") 

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

3708 

3709 for stab in reversed(self.scope[1:]): 

3710 if stab.contains(referencing_token.value): 

3711 return stab.lookup(mh, referencing_token, required_subclass) 

3712 return self.scope[0].lookup(mh, referencing_token, required_subclass) 

3713 

3714 def size(self): 

3715 return len(self.scope)