Coverage for trlc/ast.py: 90%

1317 statements  

« prev     ^ index     » next       coverage.py v7.10.7, created at 2026-09-04 11:46 +0000

1#!/usr/bin/env python3 

2# 

3# TRLC - Treat Requirements Like Code 

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

5# Copyright (C) 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 

50 

51class Value: 

52 # lobster-trace: LRM.Boolean_Values 

53 # lobster-trace: LRM.Integer_Values 

54 # lobster-trace: LRM.Decimal_Values 

55 # lobster-trace: LRM.String_Values 

56 # lobster-trace: LRM.Markup_String_Values 

57 """Polymorphic value for evaluating expressions. 

58 

59 Any record references will be fully resolved. 

60 

61 :attribute location: source location this value comes from 

62 :type: Location 

63 

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

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

66 Record_Reference, Enumeration_Literal_Spec 

67 

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

69 :type: Type 

70 """ 

71 

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

73 assert isinstance(location, Location) 

74 assert value is None or isinstance( 

75 value, 

76 ( 

77 str, 

78 int, 

79 bool, 

80 list, # for arrays 

81 dict, # for tuples 

82 Fraction, 

83 Record_Reference, 

84 Enumeration_Literal_Spec, 

85 ), 

86 ) 

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

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

89 

90 self.location = location 

91 self.value = value 

92 self.typ = typ 

93 

94 def __eq__(self, other): 

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

96 

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

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

99 

100 def resolve_references(self, mh): 

101 assert isinstance(mh, Message_Handler) 

102 

103 if isinstance(self.value, Record_Reference): 

104 self.value.resolve(mh) 

105 

106 

107############################################################################## 

108# AST Nodes 

109############################################################################## 

110 

111 

112class Node(metaclass=ABCMeta): 

113 """Base class for all AST items. 

114 

115 :attribute location: source location 

116 :type: Location 

117 """ 

118 

119 def __init__(self, location): 

120 # lobster-exclude: Constructor only declares variables 

121 assert isinstance(location, Location) 

122 self.location = location 

123 

124 def set_ast_link(self, tok): 

125 assert isinstance(tok, Token) 

126 tok.ast_link = self 

127 

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

129 # lobster-exclude: Debugging feature 

130 assert isinstance(indent, int) 

131 assert indent >= 0 

132 assert isinstance(message, str) 

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

134 

135 @abstractmethod 

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

137 """Visualise the parse tree. 

138 

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

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

141 or understanding the parse tree. The dump method will produce 

142 output like this:: 

143 

144 Symbol_Table 

145 Builtin_Boolean 

146 Builtin_Integer 

147 Builtin_Decimal 

148 Builtin_String 

149 Builtin_Markup_String 

150 Package bar 

151 Symbol_Table 

152 Record_Type MyType 

153 Composite_Component name 

154 Optional: False 

155 Type: String 

156 Checks 

157 Error 'description is too short' 

158 Anchor: description 

159 Binary Binary_Operator.COMP_GT Expression 

160 Type: Boolean 

161 Unary Unary_Operator.STRING_LENGTH Expression 

162 Type: Integer 

163 Name Reference to description 

164 Integer Literal 10 

165 Package instances 

166 Symbol_Table 

167 Record_Object SomeThing 

168 Type: MyType 

169 Field description: "Potato" 

170 Builtin_Function endswith 

171 Builtin_Function len 

172 Builtin_Function matches 

173 Builtin_Function startswith 

174 Builtin_Function oneof 

175 

176 """ 

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

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

179 # lobster-exclude: Debugging feature 

180 

181 

182class Check_Block(Node): 

183 """Node representing check blocks 

184 

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

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

187 

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

189 :type: Composite_Type 

190 

191 :attribute checks: list of checks 

192 :type: list[Check] 

193 

194 """ 

195 

196 def __init__(self, location, n_typ): 

197 # lobster-trace: LRM.Check_Block 

198 super().__init__(location) 

199 assert isinstance(n_typ, Composite_Type) 

200 self.n_typ = n_typ 

201 self.checks = [] 

202 

203 def add_check(self, n_check): 

204 # lobster-trace: LRM.Check_Evaluation_Order 

205 assert isinstance(n_check, Check) 

206 self.checks.append(n_check) 

207 

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

209 # lobster-exclude: Debugging feature 

210 self.write_indent(indent, "Check_Block") 

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

212 for n_check in self.checks: 

213 n_check.dump(indent + 1) 

214 

215 

216class Compilation_Unit(Node): 

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

218 

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

220 :type: Package 

221 

222 :attribute imports: package imported by this file 

223 :type: list[Package] 

224 

225 :attribute items: list of 

226 :type: list[Node] 

227 

228 """ 

229 

230 def __init__(self, file_name): 

231 # lobster-exclude: Constructor only declares variables 

232 super().__init__(Location(file_name)) 

233 self.package = None 

234 self.imports = None 

235 self.raw_imports = [] 

236 self.items = [] 

237 

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

239 # lobster-exclude: Debugging feature 

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

241 for t_import in self.raw_imports: 

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

243 for n_item in self.items: 

244 n_item.dump(indent + 1) 

245 

246 def set_package(self, pkg): 

247 # lobster-trace: LRM.Current_Package 

248 assert isinstance(pkg, Package) 

249 self.package = pkg 

250 

251 def add_import(self, mh, t_import): 

252 # lobster-trace: LRM.Import_Visibility 

253 # lobster-trace: LRM.Self_Imports 

254 assert isinstance(mh, Message_Handler) 

255 assert isinstance(t_import, Token) 

256 assert t_import.kind == "IDENTIFIER" 

257 

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

259 mh.error( 

260 t_import.location, "package %s cannot import itself" % self.package.name 

261 ) 

262 

263 # Skip duplicates 

264 for t_previous in self.raw_imports: 

265 if t_previous.value == t_import.value: 

266 mh.warning( 

267 t_import.location, "duplicate import of package %s" % t_import.value 

268 ) 

269 return 

270 

271 self.raw_imports.append(t_import) 

272 

273 def resolve_imports(self, mh, stab): 

274 # lobster-trace: LRM.Import_Visibility 

275 assert isinstance(mh, Message_Handler) 

276 assert isinstance(stab, Symbol_Table) 

277 self.imports = set() 

278 for t_import in self.raw_imports: 

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

280 # generate more error later. 

281 try: 

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

283 self.imports.add(a_import) 

284 a_import.set_ast_link(t_import) 

285 except TRLC_Error: 

286 pass 

287 

288 def is_visible(self, n_pkg): 

289 # lobster-trace: LRM.Import_Visibility 

290 assert self.imports is not None 

291 assert isinstance(n_pkg, Package) 

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

293 

294 def add_item(self, node): 

295 # lobster-trace: LRM.RSL_File 

296 # lobster-trace: LRM.TRLC_File 

297 assert isinstance(node, (Concrete_Type, Check_Block, Record_Object)), ( 

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

299 ) 

300 self.items.append(node) 

301 

302 

303class Check(Node): 

304 """User defined check 

305 

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

307 

308 checks T { 

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

310 ^^^^^^^^^^^^^^^^^^^^^^^1 ^2 ^3 ^4 

311 

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

313 :type: Composite_Type 

314 

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

316 :type: Expression 

317 

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

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

320 :type: Composite_Component 

321 

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

323 not specified the default is 'error') 

324 :type: str 

325 

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

327 :type: str 

328 """ 

329 

330 def __init__(self, n_type, n_expr, n_anchor, severity, t_message, extrainfo): 

331 # lobster-trace: LRM.Check_Block 

332 assert isinstance(n_type, Composite_Type) 

333 assert isinstance(n_expr, Expression) 

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

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

336 assert isinstance(t_message, Token) 

337 assert t_message.kind == "STRING" 

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

339 super().__init__(t_message.location) 

340 

341 self.n_type = n_type 

342 self.n_expr = n_expr 

343 self.n_anchor = n_anchor 

344 self.severity = severity 

345 # lobster-trace: LRM.No_Newlines_In_Message 

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

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

348 # raised is non-fatal. 

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

350 self.extrainfo = extrainfo 

351 self._uses_field_access = None 

352 

353 @property 

354 def uses_field_access(self): 

355 """Cached test: does this check's expression follow a record/union 

356 reference? 

357 

358 Returns True if any sub-expression of the check expression is a 

359 :class:`Field_Access_Expression` whose prefix has a 

360 :class:`Record_Type` or :class:`Union_Type` type. Used by 

361 the VCG to split checks into Phase A ("at declaration") and 

362 Phase B ("after references"). 

363 

364 :return: whether this check dereferences a record/union reference 

365 :rtype: bool 

366 

367 """ 

368 if self._uses_field_access is None: 

369 self._uses_field_access = self.n_expr.uses_field_access() 

370 return self._uses_field_access 

371 

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

373 # lobster-exclude: Debugging feature 

374 if self.severity == "warning": 

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

376 elif self.severity == "error": 

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

378 else: 

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

380 if self.n_anchor: 

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

382 self.n_expr.dump(indent + 1) 

383 

384 def get_real_location(self, composite_object): 

385 # lobster-exclude: LRM.Anchoring 

386 assert isinstance(composite_object, (Record_Object, Tuple_Aggregate)) 

387 if isinstance(composite_object, Record_Object): 

388 fields = composite_object.field 

389 else: 

390 fields = composite_object.value 

391 

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

393 return composite_object.location 

394 else: 

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

396 

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

398 # lobster-trace: LRM.Check_Messages 

399 # lobster-trace: LRM.Check_Severity 

400 assert isinstance(mh, Message_Handler) 

401 assert isinstance(composite_object, (Record_Object, Tuple_Aggregate)) 

402 assert isinstance(gstab, Symbol_Table) 

403 

404 if isinstance(composite_object, Record_Object): 

405 result = self.n_expr.evaluate(mh, copy(composite_object.field), gstab) 

406 else: 

407 result = self.n_expr.evaluate(mh, copy(composite_object.value), gstab) 

408 if result.value is None: 

409 loc = self.get_real_location(composite_object) 

410 mh.error( 

411 loc, 

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

413 % (self.n_expr.to_string(), mh.cross_file_reference(self.location)), 

414 ) 

415 

416 assert isinstance(result.value, bool) 

417 

418 if not result.value: 

419 loc = self.get_real_location(composite_object) 

420 if self.severity == "warning": 

421 mh.warning( 

422 location=loc, 

423 message=self.message, 

424 explanation=self.extrainfo, 

425 user=True, 

426 ) 

427 else: 

428 mh.error( 

429 location=loc, 

430 message=self.message, 

431 explanation=self.extrainfo, 

432 fatal=self.severity == "fatal", 

433 user=True, 

434 ) 

435 return False 

436 

437 return True 

438 

439 

440############################################################################## 

441# AST Nodes (Expressions) 

442############################################################################## 

443 

444 

445class Unary_Operator(Enum): 

446 # lobster-exclude: Utility enumeration for unary operators 

447 MINUS = auto() 

448 PLUS = auto() 

449 LOGICAL_NOT = auto() 

450 ABSOLUTE_VALUE = auto() 

451 

452 STRING_LENGTH = auto() 

453 ARRAY_LENGTH = auto() 

454 

455 CONVERSION_TO_INT = auto() 

456 CONVERSION_TO_DECIMAL = auto() 

457 

458 

459class Binary_Operator(Enum): 

460 # lobster-exclude: Utility enumeration for binary operators 

461 LOGICAL_AND = auto() # Short-circuit 

462 LOGICAL_OR = auto() # Short-circuit 

463 LOGICAL_XOR = auto() 

464 LOGICAL_IMPLIES = auto() # Short-circuit 

465 

466 COMP_EQ = auto() 

467 COMP_NEQ = auto() 

468 COMP_LT = auto() 

469 COMP_LEQ = auto() 

470 COMP_GT = auto() 

471 COMP_GEQ = auto() 

472 

473 STRING_CONTAINS = auto() 

474 STRING_STARTSWITH = auto() 

475 STRING_ENDSWITH = auto() 

476 STRING_REGEX = auto() 

477 

478 ARRAY_CONTAINS = auto() 

479 

480 PLUS = auto() 

481 MINUS = auto() 

482 TIMES = auto() 

483 DIVIDE = auto() 

484 REMAINDER = auto() 

485 

486 POWER = auto() 

487 

488 INDEX = auto() 

489 

490 

491class Expression(Node, metaclass=ABCMeta): 

492 """Abstract base class for all expressions. 

493 

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

495 :type: Type 

496 """ 

497 

498 def __init__(self, location, typ): 

499 # lobster-exclude: Constructor only declares variables 

500 super().__init__(location) 

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

502 self.typ = typ 

503 

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

505 """Evaluate the expression in the given context 

506 

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

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

509 dictionary that maps names (such as record fields or 

510 quantified variables) to expressions. 

511 

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

513 evaluations), otherwise it must contain the global symbol 

514 table to resolve record references. 

515 

516 :param mh: the message handler to use 

517 :type mh: Message_Handler 

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

519 :type context: dict[str, Expression] 

520 :raise TRLC_Error: if the expression cannot be evaluated 

521 :return: result of the evaluation 

522 :rtype: Value 

523 

524 """ 

525 assert isinstance(mh, Message_Handler) 

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

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

528 assert False, "evaluate not implemented for %s" % self.__class__.__name__ 

529 

530 @abstractmethod 

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

532 assert False, "to_string not implemented for %s" % self.__class__.__name__ 

533 

534 def ensure_type(self, mh, typ): 

535 # lobster-trace: LRM.Restricted_Null 

536 # lobster-trace: LRM.Null_Is_Invalid 

537 

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

539 if self.typ is None: 

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

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

542 mh.error( 

543 self.location, 

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

545 % (typ.__name__, self.typ.__class__.__name__), 

546 ) 

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

548 mh.error( 

549 self.location, 

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

551 % (typ.name, self.typ.name), 

552 ) 

553 

554 def resolve_references(self, mh): 

555 assert isinstance(mh, Message_Handler) 

556 

557 @abstractmethod 

558 def can_be_null(self): 

559 """Test if the expression could return null 

560 

561 Checks the expression if it could generate a null value 

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

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

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

565 occur earlier. 

566 

567 :return: possibility of encountering null 

568 :rtype: bool 

569 

570 """ 

571 assert False, "can_be_null not implemented for %s" % self.__class__.__name__ 

572 

573 def uses_field_access(self): 

574 """Test if this expression contains a field access on a record or 

575 union reference. 

576 

577 Returns True if any sub-expression is a 

578 :class:`Field_Access_Expression` whose prefix has a 

579 :class:`Record_Type` or :class:`Union_Type` type. This is 

580 used by the VCG to split checks into "at declaration" 

581 (Phase A) and "after references" (Phase B). 

582 

583 :return: whether this expression follows a record/union reference 

584 :rtype: bool 

585 

586 """ 

587 return False 

588 

589 

590class Implicit_Null(Expression): 

591 """Synthesised null values 

592 

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

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

595 implicit null expression for this. 

596 

597 For example given this TRLC type:: 

598 

599 type T { 

600 x optional Integer 

601 } 

602 

603 And this declaration:: 

604 

605 T Potato {} 

606 

607 Then the field mapping for Potato will be:: 

608 

609 {x: Implicit_Null} 

610 

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

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

613 that can appear in check expressions. 

614 

615 """ 

616 

617 def __init__(self, composite_object, composite_component): 

618 # lobster-trace: LRM.Unspecified_Optional_Components 

619 assert isinstance(composite_object, (Record_Object, Tuple_Aggregate)) 

620 assert isinstance(composite_component, Composite_Component) 

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

622 

623 def to_string(self): 

624 return "null" 

625 

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

627 # lobster-trace: LRM.Unspecified_Optional_Components 

628 assert isinstance(mh, Message_Handler) 

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

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

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

632 

633 def to_python_object(self): 

634 return None 

635 

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

637 # lobster-exclude: Debugging feature 

638 self.write_indent(indent, "Implicit_Null") 

639 

640 def can_be_null(self): 

641 return True 

642 

643 

644class Literal(Expression, metaclass=ABCMeta): 

645 """Abstract base for all Literals 

646 

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

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

649 check if you are dealing with a literal:: 

650 

651 isinstance(my_expression, Literal) 

652 

653 """ 

654 

655 @abstractmethod 

656 def to_python_object(self): 

657 assert False 

658 

659 

660class Null_Literal(Literal): 

661 # lobster-trace: LRM.Primary 

662 """The null literal 

663 

664 This can appear in check expressions:: 

665 

666 a /= null implies a > 5 

667 ^^^^ 

668 

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

670 values that appear in record objects. 

671 

672 """ 

673 

674 def __init__(self, token): 

675 assert isinstance(token, Token) 

676 assert token.kind == "KEYWORD" 

677 assert token.value == "null" 

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

679 

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

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

682 

683 def to_string(self): 

684 return "null" 

685 

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

687 assert isinstance(mh, Message_Handler) 

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

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

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

691 

692 def to_python_object(self): 

693 return None 

694 

695 def can_be_null(self): 

696 return True 

697 

698 

699class Integer_Literal(Literal): 

700 # lobster-trace: LRM.Integer_Values 

701 # lobster-trace: LRM.Primary 

702 """Integer literals 

703 

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

705 actually a unary negation expression, operating on a positive 

706 integer literal:: 

707 

708 x == -5 

709 

710 This would create the following tree:: 

711 

712 OP_EQUALITY 

713 NAME_REFERENCE x 

714 UNARY_EXPRESSION - 

715 INTEGER_LITERAL 5 

716 

717 :attribute value: the non-negative integer value 

718 :type: int 

719 """ 

720 

721 def __init__(self, token, typ): 

722 assert isinstance(token, Token) 

723 assert token.kind == "INTEGER" 

724 assert isinstance(typ, Builtin_Integer) 

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

726 

727 self.value = token.value 

728 

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

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

731 

732 def to_string(self): 

733 return str(self.value) 

734 

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

736 assert isinstance(mh, Message_Handler) 

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

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

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

740 

741 def to_python_object(self): 

742 return self.value 

743 

744 def can_be_null(self): 

745 return False 

746 

747 

748class Decimal_Literal(Literal): 

749 # lobster-trace: LRM.Decimal_Values 

750 # lobster-trace: LRM.Primary 

751 """Decimal literals 

752 

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

754 actually a unary negation expression, operating on a positive 

755 decimal literal:: 

756 

757 x == -5.0 

758 

759 This would create the following tree:: 

760 

761 OP_EQUALITY 

762 NAME_REFERENCE x 

763 UNARY_EXPRESSION - 

764 DECIMAL_LITERAL 5.0 

765 

766 :attribute value: the non-negative decimal value 

767 :type: fractions.Fraction 

768 """ 

769 

770 def __init__(self, token, typ): 

771 assert isinstance(token, Token) 

772 assert token.kind == "DECIMAL" 

773 assert isinstance(typ, Builtin_Decimal) 

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

775 

776 self.value = token.value 

777 

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

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

780 

781 def to_string(self): 

782 return str(self.value) 

783 

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

785 assert isinstance(mh, Message_Handler) 

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

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

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

789 

790 def to_python_object(self): 

791 return float(self.value) 

792 

793 def can_be_null(self): 

794 return False 

795 

796 

797class String_Literal(Literal): 

798 # lobster-trace: LRM.String_Values 

799 # lobster-trace: LRM.Markup_String_Values 

800 # lobster-trace: LRM.Primary 

801 """String literals 

802 

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

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

805 

806 "foo\\"bar" 

807 

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

809 

810 :attribute value: string content 

811 :type: str 

812 

813 :attribute references: resolved references of a markup string 

814 :type: list[Record_Reference] 

815 

816 """ 

817 

818 def __init__(self, token, typ): 

819 assert isinstance(token, Token) 

820 assert token.kind == "STRING" 

821 assert isinstance(typ, Builtin_String) 

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

823 

824 self.value = token.value 

825 self.has_references = isinstance(typ, Builtin_Markup_String) 

826 self.references = [] 

827 

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

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

830 if self.has_references: 

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

832 for ref in self.references: 

833 ref.dump(indent + 2) 

834 

835 def to_string(self): 

836 return self.value 

837 

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

839 assert isinstance(mh, Message_Handler) 

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

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

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

843 

844 def to_python_object(self): 

845 return self.value 

846 

847 def resolve_references(self, mh): 

848 assert isinstance(mh, Message_Handler) 

849 for ref in self.references: 

850 ref.resolve_references(mh) 

851 

852 def can_be_null(self): 

853 return False 

854 

855 

856class Boolean_Literal(Literal): 

857 # lobster-trace: LRM.Boolean_Values 

858 # lobster-trace: LRM.Primary 

859 """Boolean values 

860 

861 :attribute value: the boolean value 

862 :type: bool 

863 """ 

864 

865 def __init__(self, token, typ): 

866 assert isinstance(token, Token) 

867 assert token.kind == "KEYWORD" 

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

869 assert isinstance(typ, Builtin_Boolean) 

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

871 

872 self.value = token.value == "true" 

873 

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

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

876 

877 def to_string(self): 

878 return str(self.value) 

879 

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

881 assert isinstance(mh, Message_Handler) 

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

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

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

885 

886 def to_python_object(self): 

887 return self.value 

888 

889 def can_be_null(self): 

890 return False 

891 

892 

893class Enumeration_Literal(Literal): 

894 """Enumeration values 

895 

896 Note that this is distinct from 

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

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

899 

900 foo != my_enum.POTATO 

901 ^^^^^^^^^^^^^^ 

902 

903 To get to the string value of the enumeration literal 

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

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

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

907 ``enum_lit.value.n_typ.name``. 

908 

909 :attribute value: enumeration value 

910 :type: Enumeration_Literal_Spec 

911 

912 """ 

913 

914 def __init__(self, location, literal): 

915 # lobster-exclude: Constructor only declares variables 

916 assert isinstance(literal, Enumeration_Literal_Spec) 

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

918 

919 self.value = literal 

920 

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

922 # lobster-exclude: Debugging feature 

923 self.write_indent( 

924 indent, f"Enumeration Literal {self.typ.name}.{self.value.name}" 

925 ) 

926 

927 def to_string(self): 

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

929 

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

931 assert isinstance(mh, Message_Handler) 

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

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

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

935 

936 def to_python_object(self): 

937 return self.value.name 

938 

939 def can_be_null(self): 

940 return False 

941 

942 

943class Array_Aggregate(Expression): 

944 """Instances of array types 

945 

946 This is created when assigning to array components:: 

947 

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

949 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 

950 

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

952 limited: 

953 

954 * :class:`Literal` 

955 * :class:`Array_Aggregate` 

956 * :class:`Record_Reference` 

957 

958 :attribute value: contents of the array 

959 :type: list[Expression] 

960 

961 """ 

962 

963 def __init__(self, location, typ): 

964 # lobster-trace: LRM.Record_Object_Declaration 

965 

966 super().__init__(location, typ) 

967 self.value = [] 

968 

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

970 # lobster-exclude: Debugging feature 

971 self.write_indent(indent, "Array_Aggregate") 

972 for n_value in self.value: 

973 n_value.dump(indent + 1) 

974 

975 def append(self, value): 

976 assert isinstance( 

977 value, 

978 ( 

979 Literal, 

980 Unary_Expression, 

981 Array_Aggregate, 

982 Tuple_Aggregate, 

983 Record_Reference, 

984 ), 

985 ) 

986 self.value.append(value) 

987 

988 def to_string(self): 

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

990 

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

992 assert isinstance(mh, Message_Handler) 

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

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

995 return Value( 

996 self.location, 

997 list(element.evaluate(mh, context, gstab) for element in self.value), 

998 self.typ, 

999 ) 

1000 

1001 def resolve_references(self, mh): 

1002 assert isinstance(mh, Message_Handler) 

1003 

1004 for val in self.value: 

1005 val.resolve_references(mh) 

1006 

1007 def to_python_object(self): 

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

1009 

1010 def can_be_null(self): 

1011 return False 

1012 

1013 def uses_field_access(self): 

1014 return any(expr.uses_field_access() for expr in self.value) 

1015 

1016 

1017class Tuple_Aggregate(Expression): 

1018 """Instances of a tuple 

1019 

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

1021 two forms, the ordinary form:: 

1022 

1023 coordinate = (12.3, 40.0) 

1024 ^^^^^^^^^^^^ 

1025 

1026 And the separator form:: 

1027 

1028 item = 12345@42 

1029 ^^^^^^^^ 

1030 

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

1032 syntactic sugar. 

1033 

1034 :attribute value: contents of the tuple 

1035 :type: dict[str, Expression] 

1036 

1037 """ 

1038 

1039 def __init__(self, location, typ): 

1040 # lobster-trace: LRM.Unspecified_Optional_Components 

1041 # lobster-trace: LRM.Record_Object_Declaration 

1042 

1043 super().__init__(location, typ) 

1044 self.value = { 

1045 n_field.name: Implicit_Null(self, n_field) 

1046 for n_field in self.typ.components.values() 

1047 } 

1048 

1049 def assign(self, field, value): 

1050 assert isinstance(field, str) 

1051 assert isinstance( 

1052 value, (Literal, Unary_Expression, Tuple_Aggregate, Record_Reference) 

1053 ), "value is %s" % value.__class__.__name__ 

1054 assert field in self.typ.components 

1055 

1056 self.value[field] = value 

1057 

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

1059 # lobster-exclude: Debugging feature 

1060 self.write_indent(indent, "Tuple_Aggregate") 

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

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

1063 if isinstance(n_item, Composite_Component): 

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

1065 

1066 def to_string(self): 

1067 first = True 

1068 if self.typ.has_separators(): 

1069 rv = "" 

1070 else: 

1071 rv = "(" 

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

1073 if isinstance(n_item, Separator): 

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

1075 elif first: 

1076 first = False 

1077 else: 

1078 rv += ", " 

1079 

1080 if isinstance(n_item, Composite_Component): 

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

1082 if self.typ.has_separators(): 

1083 rv = "" 

1084 else: 

1085 rv = ")" 

1086 return rv 

1087 

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

1089 assert isinstance(mh, Message_Handler) 

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

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

1092 return Value( 

1093 self.location, 

1094 { 

1095 name: element.evaluate(mh, context, gstab) 

1096 for name, element in self.value.items() 

1097 }, 

1098 self.typ, 

1099 ) 

1100 

1101 def resolve_references(self, mh): 

1102 assert isinstance(mh, Message_Handler) 

1103 

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

1105 val.resolve_references(mh) 

1106 

1107 def to_python_object(self): 

1108 return {name: value.to_python_object() for name, value in self.value.items()} 

1109 

1110 def can_be_null(self): 

1111 return False 

1112 

1113 def uses_field_access(self): 

1114 return any(expr.uses_field_access() for expr in self.value.values()) 

1115 

1116 

1117class Record_Reference(Expression): 

1118 """Reference to another record object 

1119 

1120 This can appear in record object declarations:: 

1121 

1122 Requirement Kitten { 

1123 depends_on = Other_Package.Cat 

1124 ^1 ^2 

1125 } 

1126 

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

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

1129 the target attribute. 

1130 

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

1132 immediately resolved on parsing in the TRLC language. 

1133 

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

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

1136 in this AST node. 

1137 

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

1139 :type: str 

1140 

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

1142 :type: Record_Object 

1143 

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

1145 :type: Package 

1146 

1147 """ 

1148 

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

1150 # lobster-exclude: Constructor only declares variables 

1151 assert isinstance(location, Location) 

1152 assert isinstance(name, str) 

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

1154 assert isinstance(package, Package) 

1155 super().__init__(location, typ) 

1156 

1157 self.name = name 

1158 self.target = None 

1159 self.package = package 

1160 

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

1162 # lobster-exclude: Debugging feature 

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

1164 self.write_indent(indent + 1, f"Resolved: {self.target is not None}") 

1165 

1166 def to_string(self): 

1167 return self.name 

1168 

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

1170 assert isinstance(mh, Message_Handler) 

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

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

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

1174 

1175 def resolve_references(self, mh): 

1176 # lobster-trace: LRM.References_To_Extensions 

1177 # lobster-trace: LRM.Union_Type_Minimum_Members 

1178 assert isinstance(mh, Message_Handler) 

1179 

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

1181 mh=mh, 

1182 name=self.name, 

1183 error_location=self.location, 

1184 required_subclass=Record_Object, 

1185 ) 

1186 if self.typ is None: 

1187 self.typ = self.target.n_typ 

1188 elif isinstance(self.typ, Union_Type): 

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

1190 mh.error( 

1191 self.location, 

1192 "expected reference of type %s," 

1193 " but %s is of type %s" 

1194 % (self.typ.name, self.target.name, self.target.n_typ.name), 

1195 ) 

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

1197 mh.error( 

1198 self.location, 

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

1200 % (self.typ.name, self.target.name, self.target.n_typ.name), 

1201 ) 

1202 

1203 def to_python_object(self): 

1204 return self.target.fully_qualified_name() 

1205 

1206 def can_be_null(self): 

1207 return False 

1208 

1209 

1210class Name_Reference(Expression): 

1211 # lobster-trace: LRM.Qualified_Name 

1212 # lobster-trace: LRM.Static_Regular_Expression 

1213 

1214 """Reference to a name 

1215 

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

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

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

1219 

1220 For example:: 

1221 

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

1223 ^1 ^2 

1224 

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

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

1227 :class:`Quantified_Variable`. 

1228 

1229 :attribute entity: the entity named here 

1230 :type: Composite_Component, Quantified_Variable 

1231 """ 

1232 

1233 def __init__(self, location, entity): 

1234 assert isinstance(entity, (Composite_Component, Quantified_Variable)) 

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

1236 self.entity = entity 

1237 

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

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

1240 

1241 def to_string(self): 

1242 return self.entity.name 

1243 

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

1245 assert isinstance(mh, Message_Handler) 

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

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

1248 

1249 if context is None: 

1250 mh.error(self.location, "cannot be used in a static context") 

1251 

1252 assert self.entity.name in context 

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

1254 

1255 def can_be_null(self): 

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

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

1258 # optional. 

1259 if isinstance(self.entity, Composite_Component): 

1260 return self.entity.optional 

1261 else: 

1262 return False 

1263 

1264 

1265class Unary_Expression(Expression): 

1266 """Expression with only one operand 

1267 

1268 This captures the following operations: 

1269 

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

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

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

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

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

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

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

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

1278 

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

1280 

1281 :attribute operator: the operation 

1282 :type: Unary_Operator 

1283 

1284 :attribute n_operand: the expression we operate on 

1285 :type: Expression 

1286 

1287 """ 

1288 

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

1290 # lobster-trace: LRM.Simple_Expression 

1291 # lobster-trace: LRM.Relation 

1292 # lobster-trace: LRM.Factor 

1293 # lobster-trace: LRM.Signature_Len 

1294 # lobster-trace: LRM.Signature_Type_Conversion 

1295 

1296 super().__init__(location, typ) 

1297 assert isinstance(mh, Message_Handler) 

1298 assert isinstance(operator, Unary_Operator) 

1299 assert isinstance(n_operand, Expression) 

1300 self.operator = operator 

1301 self.n_operand = n_operand 

1302 

1303 if operator in ( 

1304 Unary_Operator.MINUS, 

1305 Unary_Operator.PLUS, 

1306 Unary_Operator.ABSOLUTE_VALUE, 

1307 ): 

1308 self.n_operand.ensure_type(mh, Builtin_Numeric_Type) 

1309 elif operator == Unary_Operator.LOGICAL_NOT: 

1310 self.n_operand.ensure_type(mh, Builtin_Boolean) 

1311 elif operator == Unary_Operator.STRING_LENGTH: 

1312 self.n_operand.ensure_type(mh, Builtin_String) 

1313 elif operator == Unary_Operator.ARRAY_LENGTH: 

1314 self.n_operand.ensure_type(mh, Array_Type) 

1315 elif operator == Unary_Operator.CONVERSION_TO_INT: 

1316 self.n_operand.ensure_type(mh, Builtin_Numeric_Type) 

1317 elif operator == Unary_Operator.CONVERSION_TO_DECIMAL: 

1318 self.n_operand.ensure_type(mh, Builtin_Numeric_Type) 

1319 else: 

1320 mh.ice_loc(self.location, "unexpected unary operation %s" % operator) 

1321 

1322 def to_string(self): 

1323 prefix_operators = { 

1324 Unary_Operator.MINUS: "-", 

1325 Unary_Operator.PLUS: "+", 

1326 Unary_Operator.ABSOLUTE_VALUE: "abs ", 

1327 Unary_Operator.LOGICAL_NOT: "not ", 

1328 } 

1329 function_calls = { 

1330 Unary_Operator.STRING_LENGTH: "len", 

1331 Unary_Operator.ARRAY_LENGTH: "len", 

1332 Unary_Operator.CONVERSION_TO_INT: "Integer", 

1333 Unary_Operator.CONVERSION_TO_DECIMAL: "Decimal", 

1334 } 

1335 

1336 if self.operator in prefix_operators: 

1337 return prefix_operators[self.operator] + self.n_operand.to_string() 

1338 

1339 elif self.operator in function_calls: 

1340 return "%s(%s)" % ( 

1341 function_calls[self.operator], 

1342 self.n_operand.to_string(), 

1343 ) 

1344 

1345 else: 

1346 assert False 

1347 

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

1349 # lobster-exclude: Debugging feature 

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

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

1352 self.n_operand.dump(indent + 1) 

1353 

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

1355 # lobster-trace: LRM.Null_Is_Invalid 

1356 # lobster-trace: LRM.Signature_Len 

1357 # lobster-trace: LRM.Signature_Type_Conversion 

1358 # lobster-trace: LRM.Len_Semantics 

1359 # lobster-trace: LRM.Integer_Conversion_Semantics 

1360 # lobster-trace: LRM.Decimal_Conversion_Semantics 

1361 

1362 assert isinstance(mh, Message_Handler) 

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

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

1365 

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

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

1368 mh.error( 

1369 v_operand.location, 

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

1371 % (self.to_string(), mh.cross_file_reference(self.location)), 

1372 ) 

1373 

1374 if self.operator == Unary_Operator.MINUS: 

1375 return Value(location=self.location, value=-v_operand.value, typ=self.typ) 

1376 elif self.operator == Unary_Operator.PLUS: 

1377 return Value(location=self.location, value=+v_operand.value, typ=self.typ) 

1378 elif self.operator == Unary_Operator.LOGICAL_NOT: 

1379 return Value( 

1380 location=self.location, value=not v_operand.value, typ=self.typ 

1381 ) 

1382 elif self.operator == Unary_Operator.ABSOLUTE_VALUE: 

1383 return Value( 

1384 location=self.location, value=abs(v_operand.value), typ=self.typ 

1385 ) 

1386 elif self.operator in ( 

1387 Unary_Operator.STRING_LENGTH, 

1388 Unary_Operator.ARRAY_LENGTH, 

1389 ): 

1390 return Value( 

1391 location=self.location, value=len(v_operand.value), typ=self.typ 

1392 ) 

1393 elif self.operator == Unary_Operator.CONVERSION_TO_INT: 

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

1395 return Value( 

1396 location=self.location, 

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

1398 typ=self.typ, 

1399 ) 

1400 else: 

1401 return Value( 

1402 location=self.location, value=v_operand.value, typ=self.typ 

1403 ) 

1404 elif self.operator == Unary_Operator.CONVERSION_TO_DECIMAL: 

1405 return Value( 

1406 location=self.location, value=Fraction(v_operand.value), typ=self.typ 

1407 ) 

1408 else: 

1409 mh.ice_loc(self.location, "unexpected unary operation %s" % self.operator) 

1410 

1411 def to_python_object(self): 

1412 assert self.operator in (Unary_Operator.MINUS, Unary_Operator.PLUS) 

1413 val = self.n_operand.to_python_object() 

1414 if self.operator == Unary_Operator.MINUS: 

1415 return -val 

1416 else: 

1417 return val 

1418 

1419 def can_be_null(self): 

1420 return False 

1421 

1422 def uses_field_access(self): 

1423 return self.n_operand.uses_field_access() 

1424 

1425 

1426class Binary_Expression(Expression): 

1427 """Expression with two operands 

1428 

1429 This captures the following operations: 

1430 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1453 

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

1455 

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

1457 rationals and strings. 

1458 

1459 :attribute operator: the operation 

1460 :type: Binary_Operator 

1461 

1462 :attribute n_lhs: the first operand 

1463 :type: Expression 

1464 

1465 :attribute n_rhs: the second operand 

1466 :type: Expression 

1467 

1468 """ 

1469 

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

1471 # lobster-trace: LRM.Expression 

1472 # lobster-trace: LRM.Relation 

1473 # lobster-trace: LRM.Simple_Expression 

1474 # lobster-trace: LRM.Term 

1475 # lobster-trace: LRM.Factor 

1476 # lobster-trace: LRM.Signature_String_End_Functions 

1477 # lobster-trace: LRM.Signature_Matches 

1478 

1479 super().__init__(location, typ) 

1480 assert isinstance(mh, Message_Handler) 

1481 assert isinstance(operator, Binary_Operator) 

1482 assert isinstance(n_lhs, Expression) 

1483 assert isinstance(n_rhs, Expression) 

1484 self.operator = operator 

1485 self.n_lhs = n_lhs 

1486 self.n_rhs = n_rhs 

1487 

1488 if operator in ( 

1489 Binary_Operator.LOGICAL_AND, 

1490 Binary_Operator.LOGICAL_OR, 

1491 Binary_Operator.LOGICAL_XOR, 

1492 Binary_Operator.LOGICAL_IMPLIES, 

1493 ): 

1494 self.n_lhs.ensure_type(mh, Builtin_Boolean) 

1495 self.n_rhs.ensure_type(mh, Builtin_Boolean) 

1496 

1497 elif operator in (Binary_Operator.COMP_EQ, Binary_Operator.COMP_NEQ): 

1498 # lobster-trace: LRM.Union_Type_Equality 

1499 # lobster-trace: LRM.Union_Type_Equality_Domain 

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

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

1502 pass 

1503 elif isinstance(self.n_lhs.typ, Union_Type) or isinstance( 

1504 self.n_rhs.typ, Union_Type 

1505 ): 

1506 # For union types, we allow comparison if both 

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

1508 lhs_is_record = isinstance(self.n_lhs.typ, (Record_Type, Union_Type)) 

1509 rhs_is_record = isinstance(self.n_rhs.typ, (Record_Type, Union_Type)) 

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

1511 mh.error( 

1512 self.location, 

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

1514 % (self.n_lhs.typ.name, self.n_rhs.typ.name), 

1515 ) 

1516 else: 

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

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

1519 # of the other. This implements Equality_Domain for 

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

1521 lhs_members = ( 

1522 self.n_lhs.typ.types 

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

1524 else [self.n_lhs.typ] 

1525 ) 

1526 rhs_members = ( 

1527 self.n_rhs.typ.types 

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

1529 else [self.n_rhs.typ] 

1530 ) 

1531 if not any( 

1532 lm.is_subclass_of(rm) or rm.is_subclass_of(lm) 

1533 for lm in lhs_members 

1534 for rm in rhs_members 

1535 ): 

1536 mh.error( 

1537 self.location, 

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

1539 % (self.n_lhs.typ.name, self.n_rhs.typ.name), 

1540 ) 

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

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

1543 # types match 

1544 mh.error( 

1545 self.location, 

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

1547 % (self.n_lhs.typ.name, self.n_rhs.typ.name), 

1548 ) 

1549 

1550 elif operator in ( 

1551 Binary_Operator.COMP_LT, 

1552 Binary_Operator.COMP_LEQ, 

1553 Binary_Operator.COMP_GT, 

1554 Binary_Operator.COMP_GEQ, 

1555 ): 

1556 self.n_lhs.ensure_type(mh, Builtin_Numeric_Type) 

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

1558 

1559 elif operator in ( 

1560 Binary_Operator.STRING_CONTAINS, 

1561 Binary_Operator.STRING_STARTSWITH, 

1562 Binary_Operator.STRING_ENDSWITH, 

1563 Binary_Operator.STRING_REGEX, 

1564 ): 

1565 self.n_lhs.ensure_type(mh, Builtin_String) 

1566 self.n_rhs.ensure_type(mh, Builtin_String) 

1567 

1568 elif operator == Binary_Operator.ARRAY_CONTAINS: 

1569 self.n_rhs.ensure_type(mh, Array_Type) 

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

1571 

1572 elif operator == Binary_Operator.PLUS: 

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

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

1575 else: 

1576 self.n_lhs.ensure_type(mh, Builtin_String) 

1577 self.n_rhs.ensure_type(mh, Builtin_String) 

1578 

1579 elif operator in ( 

1580 Binary_Operator.MINUS, 

1581 Binary_Operator.TIMES, 

1582 Binary_Operator.DIVIDE, 

1583 ): 

1584 self.n_lhs.ensure_type(mh, Builtin_Numeric_Type) 

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

1586 

1587 elif operator == Binary_Operator.POWER: 

1588 self.n_lhs.ensure_type(mh, Builtin_Numeric_Type) 

1589 self.n_rhs.ensure_type(mh, Builtin_Integer) 

1590 

1591 elif operator == Binary_Operator.REMAINDER: 

1592 self.n_lhs.ensure_type(mh, Builtin_Integer) 

1593 self.n_rhs.ensure_type(mh, Builtin_Integer) 

1594 

1595 elif operator == Binary_Operator.INDEX: 

1596 self.n_lhs.ensure_type(mh, Array_Type) 

1597 self.n_rhs.ensure_type(mh, Builtin_Integer) 

1598 

1599 else: 

1600 mh.ice_loc(self.location, "unexpected binary operation %s" % operator) 

1601 

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

1603 # lobster-exclude: Debugging feature 

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

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

1606 self.n_lhs.dump(indent + 1) 

1607 self.n_rhs.dump(indent + 1) 

1608 

1609 def to_string(self): 

1610 infix_operators = { 

1611 Binary_Operator.LOGICAL_AND: "and", 

1612 Binary_Operator.LOGICAL_OR: "or", 

1613 Binary_Operator.LOGICAL_XOR: "xor", 

1614 Binary_Operator.LOGICAL_IMPLIES: "implies", 

1615 Binary_Operator.COMP_EQ: "==", 

1616 Binary_Operator.COMP_NEQ: "!=", 

1617 Binary_Operator.COMP_LT: "<", 

1618 Binary_Operator.COMP_LEQ: "<=", 

1619 Binary_Operator.COMP_GT: ">", 

1620 Binary_Operator.COMP_GEQ: ">=", 

1621 Binary_Operator.STRING_CONTAINS: "in", 

1622 Binary_Operator.ARRAY_CONTAINS: "in", 

1623 Binary_Operator.PLUS: "+", 

1624 Binary_Operator.MINUS: "-", 

1625 Binary_Operator.TIMES: "*", 

1626 Binary_Operator.DIVIDE: "/", 

1627 Binary_Operator.REMAINDER: "%", 

1628 Binary_Operator.POWER: "**", 

1629 } 

1630 string_functions = { 

1631 Binary_Operator.STRING_STARTSWITH: "startswith", 

1632 Binary_Operator.STRING_ENDSWITH: "endswith", 

1633 Binary_Operator.STRING_REGEX: "matches", 

1634 } 

1635 

1636 if self.operator in infix_operators: 

1637 return "%s %s %s" % ( 

1638 self.n_lhs.to_string(), 

1639 infix_operators[self.operator], 

1640 self.n_rhs.to_string(), 

1641 ) 

1642 

1643 elif self.operator in string_functions: 

1644 return "%s(%s, %s)" % ( 

1645 string_functions[self.operator], 

1646 self.n_lhs.to_string(), 

1647 self.n_rhs.to_string(), 

1648 ) 

1649 

1650 elif self.operator == Binary_Operator.INDEX: 

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

1652 

1653 else: 

1654 assert False 

1655 

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

1657 # lobster-trace: LRM.Null_Equivalence 

1658 # lobster-trace: LRM.Null_Is_Invalid 

1659 # lobster-trace: LRM.Signature_String_End_Functions 

1660 # lobster-trace: LRM.Signature_Matches 

1661 # lobster-trace: LRM.Startswith_Semantics 

1662 # lobster-trace: LRM.Endswith_Semantics 

1663 # lobster-trace: LRM.Matches_Semantics 

1664 

1665 assert isinstance(mh, Message_Handler) 

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

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

1668 

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

1670 if v_lhs.value is None and self.operator not in ( 

1671 Binary_Operator.COMP_EQ, 

1672 Binary_Operator.COMP_NEQ, 

1673 ): 

1674 mh.error( 

1675 v_lhs.location, 

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

1677 % (self.to_string(), mh.cross_file_reference(self.location)), 

1678 ) 

1679 

1680 # Check for the short-circuit operators first 

1681 if self.operator == Binary_Operator.LOGICAL_AND: 

1682 assert isinstance(v_lhs.value, bool) 

1683 if v_lhs.value: 

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

1685 else: 

1686 return v_lhs 

1687 

1688 elif self.operator == Binary_Operator.LOGICAL_OR: 

1689 assert isinstance(v_lhs.value, bool) 

1690 if v_lhs.value: 

1691 return v_lhs 

1692 else: 

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

1694 

1695 elif self.operator == Binary_Operator.LOGICAL_IMPLIES: 

1696 assert isinstance(v_lhs.value, bool) 

1697 if v_lhs.value: 

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

1699 else: 

1700 return Value(location=self.location, value=True, typ=self.typ) 

1701 

1702 # Otherwise, evaluate RHS and do the operation 

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

1704 if v_rhs.value is None and self.operator not in ( 

1705 Binary_Operator.COMP_EQ, 

1706 Binary_Operator.COMP_NEQ, 

1707 ): 

1708 mh.error( 

1709 v_rhs.location, 

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

1711 % (self.to_string(), mh.cross_file_reference(self.location)), 

1712 ) 

1713 

1714 if self.operator == Binary_Operator.LOGICAL_XOR: 

1715 assert isinstance(v_lhs.value, bool) 

1716 assert isinstance(v_rhs.value, bool) 

1717 return Value( 

1718 location=self.location, value=v_lhs.value ^ v_rhs.value, typ=self.typ 

1719 ) 

1720 

1721 elif self.operator == Binary_Operator.COMP_EQ: 

1722 return Value( 

1723 location=self.location, value=v_lhs.value == v_rhs.value, typ=self.typ 

1724 ) 

1725 

1726 elif self.operator == Binary_Operator.COMP_NEQ: 

1727 return Value( 

1728 location=self.location, value=v_lhs.value != v_rhs.value, typ=self.typ 

1729 ) 

1730 

1731 elif self.operator in ( 

1732 Binary_Operator.COMP_LT, 

1733 Binary_Operator.COMP_LEQ, 

1734 Binary_Operator.COMP_GT, 

1735 Binary_Operator.COMP_GEQ, 

1736 ): 

1737 return Value( 

1738 location=self.location, 

1739 value={ 

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

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

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

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

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

1745 typ=self.typ, 

1746 ) 

1747 

1748 elif self.operator == Binary_Operator.STRING_CONTAINS: 

1749 assert isinstance(v_lhs.value, str) 

1750 assert isinstance(v_rhs.value, str) 

1751 

1752 return Value( 

1753 location=self.location, value=v_lhs.value in v_rhs.value, typ=self.typ 

1754 ) 

1755 

1756 elif self.operator == Binary_Operator.STRING_STARTSWITH: 

1757 assert isinstance(v_lhs.value, str) 

1758 assert isinstance(v_rhs.value, str) 

1759 return Value( 

1760 location=self.location, 

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

1762 typ=self.typ, 

1763 ) 

1764 

1765 elif self.operator == Binary_Operator.STRING_ENDSWITH: 

1766 assert isinstance(v_lhs.value, str) 

1767 assert isinstance(v_rhs.value, str) 

1768 return Value( 

1769 location=self.location, 

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

1771 typ=self.typ, 

1772 ) 

1773 

1774 elif self.operator == Binary_Operator.STRING_REGEX: 

1775 assert isinstance(v_lhs.value, str) 

1776 assert isinstance(v_rhs.value, str) 

1777 return Value( 

1778 location=self.location, 

1779 value=re.match(v_rhs.value, v_lhs.value) is not None, 

1780 typ=self.typ, 

1781 ) 

1782 

1783 elif self.operator == Binary_Operator.ARRAY_CONTAINS: 

1784 assert isinstance(v_rhs.value, list) 

1785 

1786 return Value( 

1787 location=self.location, value=v_lhs in v_rhs.value, typ=self.typ 

1788 ) 

1789 

1790 elif self.operator == Binary_Operator.PLUS: 

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

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

1793 return Value( 

1794 location=self.location, value=v_lhs.value + v_rhs.value, typ=self.typ 

1795 ) 

1796 

1797 elif self.operator == Binary_Operator.MINUS: 

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

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

1800 return Value( 

1801 location=self.location, value=v_lhs.value - v_rhs.value, typ=self.typ 

1802 ) 

1803 

1804 elif self.operator == Binary_Operator.TIMES: 

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

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

1807 return Value( 

1808 location=self.location, value=v_lhs.value * v_rhs.value, typ=self.typ 

1809 ) 

1810 

1811 elif self.operator == Binary_Operator.DIVIDE: 

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

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

1814 

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

1816 mh.error( 

1817 v_rhs.location, 

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

1819 % (self.to_string(), mh.cross_file_reference(self.location)), 

1820 ) 

1821 

1822 if isinstance(v_lhs.value, int): 

1823 return Value( 

1824 location=self.location, 

1825 value=v_lhs.value // v_rhs.value, 

1826 typ=self.typ, 

1827 ) 

1828 else: 

1829 return Value( 

1830 location=self.location, 

1831 value=v_lhs.value / v_rhs.value, 

1832 typ=self.typ, 

1833 ) 

1834 

1835 elif self.operator == Binary_Operator.REMAINDER: 

1836 assert isinstance(v_lhs.value, int) 

1837 assert isinstance(v_rhs.value, int) 

1838 

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

1840 mh.error( 

1841 v_rhs.location, 

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

1843 % (self.to_string(), mh.cross_file_reference(self.location)), 

1844 ) 

1845 

1846 return Value( 

1847 location=self.location, 

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

1849 typ=self.typ, 

1850 ) 

1851 

1852 elif self.operator == Binary_Operator.POWER: 

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

1854 assert isinstance(v_rhs.value, int) 

1855 return Value( 

1856 location=self.location, value=v_lhs.value**v_rhs.value, typ=self.typ 

1857 ) 

1858 

1859 elif self.operator == Binary_Operator.INDEX: 

1860 assert isinstance(v_lhs.value, list) 

1861 assert isinstance(v_rhs.value, int) 

1862 

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

1864 mh.error( 

1865 v_rhs.location, 

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

1867 % (self.to_string(), mh.cross_file_reference(self.location)), 

1868 ) 

1869 elif ( 1869 ↛ 1873line 1869 didn't jump to line 1873 because the condition on line 1869 was never true

1870 v_lhs.typ.upper_bound is not None 

1871 and v_rhs.value > v_lhs.typ.upper_bound 

1872 ): 

1873 mh.error( 

1874 v_rhs.location, 

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

1876 % ( 

1877 v_lhs.typ.upper_bound, 

1878 self.to_string(), 

1879 mh.cross_file_reference(self.location), 

1880 ), 

1881 ) 

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

1883 mh.error( 

1884 v_lhs.location, 

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

1886 % (self.to_string(), mh.cross_file_reference(self.location)), 

1887 ) 

1888 

1889 return Value( 

1890 location=self.location, 

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

1892 typ=self.typ, 

1893 ) 

1894 

1895 else: 

1896 mh.ice_loc(self.location, "unexpected binary operator %s" % self.operator) 

1897 

1898 def can_be_null(self): 

1899 return False 

1900 

1901 def uses_field_access(self): 

1902 return self.n_lhs.uses_field_access() or self.n_rhs.uses_field_access() 

1903 

1904 

1905class Field_Access_Expression(Expression): 

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

1907 

1908 For example in:: 

1909 

1910 foo.bar 

1911 ^1 ^2 

1912 

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

1914 :type: Expression 

1915 

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

1917 :type: Composite_Component 

1918 

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

1920 :type: bool 

1921 

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

1923 Only meaningful when is_union_access is True. 

1924 :type: bool 

1925 

1926 """ 

1927 

1928 def __init__( 

1929 self, mh, location, n_prefix, n_field, is_union_access=False, is_universal=True 

1930 ): 

1931 # lobster-trace: LRM.Union_Type_Field_Access 

1932 assert isinstance(mh, Message_Handler) 

1933 assert isinstance(n_prefix, Expression) 

1934 assert isinstance(n_field, Composite_Component) 

1935 assert isinstance(is_union_access, bool) 

1936 assert isinstance(is_universal, bool) 

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

1938 self.n_prefix = n_prefix 

1939 self.n_field = n_field 

1940 self.is_union_access = is_union_access 

1941 self.is_universal = is_universal 

1942 

1943 if not is_union_access: 

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

1945 

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

1947 # lobster-exclude: Debugging feature 

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

1949 self.n_prefix.dump(indent + 1) 

1950 

1951 def to_string(self): 

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

1953 

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

1955 assert isinstance(mh, Message_Handler) 

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

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

1958 

1959 v_prefix = self.n_prefix.evaluate(mh, context, gstab).value 

1960 if v_prefix is None: 

1961 # lobster-trace: LRM.Dereference 

1962 mh.error(self.n_prefix.location, "null dereference") 

1963 

1964 # lobster-trace: LRM.Union_Type_Partial_Field_Access 

1965 # lobster-trace: LRM.Union_Type_Partial_Field_Null 

1966 if self.n_field.name not in v_prefix: 

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

1968 

1969 v_field = v_prefix[self.n_field.name] 

1970 if isinstance(v_field, Expression): 

1971 # lobster-trace: LRM.Dereference 

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

1973 else: 

1974 return v_field 

1975 

1976 def can_be_null(self): 

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

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

1979 # report True in that case. 

1980 return self.is_union_access and not self.is_universal 

1981 

1982 def uses_field_access(self): 

1983 # lobster-trace: LRM.Dereference 

1984 if isinstance(self.n_prefix.typ, (Record_Type, Union_Type)): 

1985 return True 

1986 return self.n_prefix.uses_field_access() 

1987 

1988 

1989class Range_Test(Expression): 

1990 """Range membership test 

1991 

1992 For example in:: 

1993 

1994 x in 1 .. field+1 

1995 ^lhs ^lower ^^^^^^^upper 

1996 

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

1998 you can have arbitrarily complex expressions here. 

1999 

2000 :attribute n_lhs: the expression to test 

2001 :type: Expression 

2002 

2003 :attribute n_lower: the lower bound 

2004 :type: Expression 

2005 

2006 :attribute n_upper: the upper bound 

2007 :type: Expression 

2008 

2009 """ 

2010 

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

2012 # lobster-trace: LRM.Relation 

2013 super().__init__(location, typ) 

2014 assert isinstance(mh, Message_Handler) 

2015 assert isinstance(n_lhs, Expression) 

2016 assert isinstance(n_lower, Expression) 

2017 assert isinstance(n_upper, Expression) 

2018 self.n_lhs = n_lhs 

2019 self.n_lower = n_lower 

2020 self.n_upper = n_upper 

2021 

2022 self.n_lhs.ensure_type(mh, Builtin_Numeric_Type) 

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

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

2025 

2026 def to_string(self): 

2027 return "%s in %s .. %s" % ( 

2028 self.n_lhs.to_string(), 

2029 self.n_lower.to_string(), 

2030 self.n_upper.to_string(), 

2031 ) 

2032 

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

2034 # lobster-exclude: Debugging feature 

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

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

2037 self.n_lhs.dump(indent + 1) 

2038 self.n_lower.dump(indent + 1) 

2039 self.n_upper.dump(indent + 1) 

2040 

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

2042 # lobster-trace: LRM.Null_Is_Invalid 

2043 assert isinstance(mh, Message_Handler) 

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

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

2046 

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

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

2049 mh.error( 

2050 v_lhs.location, 

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

2052 % (self.to_string(), mh.cross_file_reference(self.location)), 

2053 ) 

2054 

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

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

2057 mh.error( 

2058 v_lower.location, 

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

2060 % (self.to_string(), mh.cross_file_reference(self.location)), 

2061 ) 

2062 

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

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

2065 mh.error( 

2066 v_upper.location, 

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

2068 % (self.to_string(), mh.cross_file_reference(self.location)), 

2069 ) 

2070 

2071 return Value( 

2072 location=self.location, 

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

2074 typ=self.typ, 

2075 ) 

2076 

2077 def can_be_null(self): 

2078 return False 

2079 

2080 def uses_field_access(self): 

2081 return ( 

2082 self.n_lhs.uses_field_access() 

2083 or self.n_lower.uses_field_access() 

2084 or self.n_upper.uses_field_access() 

2085 ) 

2086 

2087 

2088class OneOf_Expression(Expression): 

2089 """OneOf expression 

2090 

2091 For example in:: 

2092 

2093 oneof(a, b, c) 

2094 ^^^^^^^ choices 

2095 

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

2097 :type: list[Expression] 

2098 """ 

2099 

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

2101 # lobster-trace: LRM.Signature_OneOf 

2102 super().__init__(location, typ) 

2103 assert isinstance(typ, Builtin_Boolean) 

2104 assert isinstance(mh, Message_Handler) 

2105 assert isinstance(choices, list) 

2106 assert all(isinstance(item, Expression) for item in choices) 

2107 self.choices = choices 

2108 

2109 for n_choice in choices: 

2110 n_choice.ensure_type(mh, Builtin_Boolean) 

2111 

2112 def to_string(self): 

2113 return "oneof(%s)" % ", ".join( 

2114 n_choice.to_string() for n_choice in self.choices 

2115 ) 

2116 

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

2118 # lobster-exclude: Debugging feature 

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

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

2121 for n_choice in self.choices: 

2122 n_choice.dump(indent + 1) 

2123 

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

2125 # lobster-trace: LRM.OneOf_Semantics 

2126 assert isinstance(mh, Message_Handler) 

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

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

2129 

2130 v_choices = [ 

2131 n_choice.evaluate(mh, context, gstab).value for n_choice in self.choices 

2132 ] 

2133 

2134 return Value( 

2135 location=self.location, value=v_choices.count(True) == 1, typ=self.typ 

2136 ) 

2137 

2138 def can_be_null(self): 

2139 return False 

2140 

2141 def uses_field_access(self): 

2142 return any(n_choice.uses_field_access() for n_choice in self.choices) 

2143 

2144 

2145class Action(Node): 

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

2147 

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

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

2150 Actions:: 

2151 

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

2153 ^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^ 

2154 

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

2156 the :class:`Conditional_Expression` itself. 

2157 

2158 :attribute kind: Either if or elseif 

2159 :type: str 

2160 

2161 :attribute n_cond: The boolean condition expression 

2162 :type: Expression 

2163 

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

2165 :type: Expression 

2166 

2167 """ 

2168 

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

2170 # lobster-trace: LRM.Conditional_Expression 

2171 assert isinstance(mh, Message_Handler) 

2172 assert isinstance(t_kind, Token) 

2173 assert t_kind.kind == "KEYWORD" 

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

2175 assert isinstance(n_condition, Expression) 

2176 assert isinstance(n_expression, Expression) 

2177 super().__init__(t_kind.location) 

2178 self.kind = t_kind.value 

2179 self.n_cond = n_condition 

2180 self.n_expr = n_expression 

2181 # lobster-trace: LRM.Conditional_Expression_Types 

2182 self.n_cond.ensure_type(mh, Builtin_Boolean) 

2183 

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

2185 # lobster-exclude: Debugging feature 

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

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

2188 self.n_cond.dump(indent + 2) 

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

2190 self.n_expr.dump(indent + 2) 

2191 

2192 def to_string(self): 

2193 return "%s %s then %s" % ( 

2194 self.kind, 

2195 self.n_cond.to_string(), 

2196 self.n_expr.to_string(), 

2197 ) 

2198 

2199 

2200class Conditional_Expression(Expression): 

2201 """A conditional expression 

2202 

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

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

2205 expression with two Actions:: 

2206 

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

2208 ^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^ 

2209 

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

2211 

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

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

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

2215 of all actions and the else expression will match. 

2216 

2217 :attribute actions: a list of Actions 

2218 :type: list[Action] 

2219 

2220 :attribute else_expr: the else expression 

2221 :type: Expression 

2222 

2223 """ 

2224 

2225 def __init__(self, location, if_action): 

2226 # lobster-trace: LRM.Conditional_Expression 

2227 assert isinstance(if_action, Action) 

2228 assert if_action.kind == "if" 

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

2230 self.actions = [if_action] 

2231 self.else_expr = None 

2232 

2233 def add_elsif(self, mh, n_action): 

2234 # lobster-trace: LRM.Conditional_Expression 

2235 # lobster-trace; LRM.Conditional_Expression_Types 

2236 assert isinstance(mh, Message_Handler) 

2237 assert isinstance(n_action, Action) 

2238 assert n_action.kind == "elsif" 

2239 

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

2241 self.actions.append(n_action) 

2242 

2243 def set_else_part(self, mh, n_expr): 

2244 # lobster-trace: LRM.Conditional_Expression 

2245 # lobster-trace; LRM.Conditional_Expression_Types 

2246 assert isinstance(mh, Message_Handler) 

2247 assert isinstance(n_expr, Expression) 

2248 

2249 n_expr.ensure_type(mh, self.typ) 

2250 self.else_expr = n_expr 

2251 

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

2253 # lobster-exclude: Debugging feature 

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

2255 for action in self.actions: 

2256 action.dump(indent + 1) 

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

2258 self.else_expr.dump(indent + 2) 

2259 

2260 def to_string(self): 

2261 rv = "(" + " ".join(action.to_string() for action in self.actions) 

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

2263 rv += ")" 

2264 return rv 

2265 

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

2267 # lobster-trace: LRM.Conditional_Expression_Else 

2268 # lobster-trace: LRM.Conditional_Expression_Evaluation 

2269 # lobster-trace: LRM.Null_Is_Invalid 

2270 assert isinstance(mh, Message_Handler) 

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

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

2273 

2274 for action in self.actions: 

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

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

2277 mh.error( 

2278 v_cond.location, 

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

2280 % (action.to_string(), mh.cross_file_reference(self.location)), 

2281 ) 

2282 if v_cond.value: 

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

2284 

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

2286 

2287 def can_be_null(self): 

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

2289 return True 

2290 

2291 return any(action.n_expr.can_be_null() for action in self.actions) 

2292 

2293 def uses_field_access(self): 

2294 return any( 

2295 action.n_cond.uses_field_access() or action.n_expr.uses_field_access() 

2296 for action in self.actions 

2297 ) or (self.else_expr is not None and self.else_expr.uses_field_access()) 

2298 

2299 

2300class Quantified_Expression(Expression): 

2301 """A quantified expression 

2302 

2303 For example:: 

2304 

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

2306 ^4 ^1 ^2 ^^^^^3 

2307 

2308 A quantified expression introduces and binds a 

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

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

2311 each component of the source in turn. 

2312 

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

2314 :type: Quantified_Variable 

2315 

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

2317 :type: Name_Reference 

2318 

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

2320 :type: Expression 

2321 

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

2323 :type: Boolean 

2324 

2325 """ 

2326 

2327 def __init__(self, mh, location, typ, universal, n_variable, n_source, n_expr): 

2328 # lobster-trace: LRM.Quantified_Expression 

2329 # lobster-trace: LRM.Quantification_Type 

2330 super().__init__(location, typ) 

2331 assert isinstance(typ, Builtin_Boolean) 

2332 assert isinstance(universal, bool) 

2333 assert isinstance(n_variable, Quantified_Variable) 

2334 assert isinstance(n_expr, Expression) 

2335 assert isinstance(n_source, Name_Reference) 

2336 self.universal = universal 

2337 self.n_var = n_variable 

2338 self.n_expr = n_expr 

2339 self.n_source = n_source 

2340 self.n_expr.ensure_type(mh, Builtin_Boolean) 

2341 

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

2343 # lobster-exclude: Debugging feature 

2344 if self.universal: 

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

2346 else: 

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

2348 self.n_var.dump(indent + 1) 

2349 self.n_expr.dump(indent + 1) 

2350 

2351 def to_string(self): 

2352 return "(%s %s in %s => %s)" % ( 

2353 "forall" if self.universal else "exists", 

2354 self.n_var.name, 

2355 self.n_source.to_string(), 

2356 self.n_expr.to_string(), 

2357 ) 

2358 

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

2360 # lobster-trace: LRM.Null_Is_Invalid 

2361 # lobster-trace: LRM.Universal_Quantification_Semantics 

2362 # lobster-trace: LRM.Existential_Quantification_Semantics 

2363 assert isinstance(mh, Message_Handler) 

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

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

2366 

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

2368 new_ctx = {} 

2369 else: 

2370 new_ctx = copy(context) 

2371 

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

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

2374 # error messages. 

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

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

2377 if isinstance(array_values, Implicit_Null): 

2378 mh.error( 

2379 array_values.location, 

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

2381 "must not be null" 

2382 % ( 

2383 self.n_source.to_string(), 

2384 self.to_string(), 

2385 mh.cross_file_reference(self.location), 

2386 ), 

2387 ) 

2388 else: 

2389 assert isinstance(array_values, Array_Aggregate) 

2390 

2391 rv = self.universal 

2392 loc = self.location 

2393 for binding in array_values.value: 

2394 new_ctx[self.n_var.name] = binding 

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

2396 assert isinstance(result.value, bool) 

2397 if self.universal and not result.value: 

2398 rv = False 

2399 loc = binding.location 

2400 break 

2401 elif not self.universal and result.value: 

2402 rv = True 

2403 loc = binding.location 

2404 break 

2405 

2406 return Value(location=loc, value=rv, typ=self.typ) 

2407 

2408 def can_be_null(self): 

2409 return False 

2410 

2411 def uses_field_access(self): 

2412 return self.n_source.uses_field_access() or self.n_expr.uses_field_access() 

2413 

2414 

2415############################################################################## 

2416# AST Nodes (Entities) 

2417############################################################################## 

2418 

2419 

2420class Entity(Node, metaclass=ABCMeta): 

2421 """Base class for all entities. 

2422 

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

2424 allocate memory. Examples of entities are types and record 

2425 objects. 

2426 

2427 :attribute name: unqualified name of the entity 

2428 :type: str 

2429 

2430 """ 

2431 

2432 def __init__(self, name, location): 

2433 # lobster-trace: LRM.Described_Name_Equality 

2434 super().__init__(location) 

2435 assert isinstance(name, str) 

2436 self.name = name 

2437 

2438 

2439class Typed_Entity(Entity, metaclass=ABCMeta): 

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

2441 

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

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

2444 are record objects and components. 

2445 

2446 :attribute n_typ: type of the entity 

2447 :type: Type 

2448 

2449 """ 

2450 

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

2452 # lobster-exclude: Constructor only declares variables 

2453 super().__init__(name, location) 

2454 assert isinstance(n_typ, Type) 

2455 self.n_typ = n_typ 

2456 

2457 

2458class Quantified_Variable(Typed_Entity): 

2459 """Variable used in quantified expression. 

2460 

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

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

2463 

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

2465 ^ 

2466 

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

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

2469 

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

2471 :type: Type 

2472 

2473 """ 

2474 

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

2476 # lobster-exclude: Debugging feature 

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

2478 self.n_typ.dump(indent + 1) 

2479 

2480 

2481class Type(Entity, metaclass=ABCMeta): 

2482 """Abstract base class for all types.""" 

2483 

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

2485 assert isinstance(mh, Message_Handler) 

2486 assert isinstance(value, Expression) 

2487 assert isinstance(gstab, Symbol_Table) 

2488 return True 

2489 

2490 def get_example_value(self): 

2491 # lobster-exclude: utility method 

2492 assert False 

2493 

2494 

2495class Concrete_Type(Type, metaclass=ABCMeta): 

2496 # lobster-trace: LRM.Type_Declarations 

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

2498 

2499 :attribute n_package: package where this type was declared 

2500 :type: Package 

2501 """ 

2502 

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

2504 super().__init__(name, location) 

2505 assert isinstance(n_package, Package) 

2506 self.n_package = n_package 

2507 

2508 def fully_qualified_name(self): 

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

2510 

2511 :returns: the type's full name 

2512 :rtype: str 

2513 """ 

2514 return self.n_package.name + "." + self.name 

2515 

2516 def __hash__(self): 

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

2518 

2519 def __repr__(self): 

2520 return "%s<%s>" % (self.__class__.__name__, self.fully_qualified_name()) 

2521 

2522 

2523class Builtin_Type(Type, metaclass=ABCMeta): 

2524 # lobster-trace: LRM.Builtin_Types 

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

2526 

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

2528 

2529 def __init__(self, name): 

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

2531 

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

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

2534 

2535 

2536class Builtin_Numeric_Type(Builtin_Type, metaclass=ABCMeta): 

2537 # lobster-trace: LRM.Builtin_Types 

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

2539 

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

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

2542 

2543 

2544class Builtin_Function(Entity): 

2545 # lobster-trace: LRM.Builtin_Functions 

2546 """Builtin functions. 

2547 

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

2549 

2550 :attribute arity: number of parameters 

2551 :type: int 

2552 

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

2554 :type: bool 

2555 

2556 """ 

2557 

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

2559 

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

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

2562 assert isinstance(arity, int) 

2563 assert isinstance(arity_at_least, bool) 

2564 assert arity >= 0 

2565 self.arity = arity 

2566 self.arity_at_least = arity_at_least 

2567 

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

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

2570 

2571 

2572class Array_Type(Type): 

2573 """Anonymous array type. 

2574 

2575 These are declared implicitly for each record component that has 

2576 an array specifier:: 

2577 

2578 foo Integer [5 .. *] 

2579 ^ 

2580 

2581 :attribute lower_bound: minimum number of elements 

2582 :type: int 

2583 

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

2585 :type: Location 

2586 

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

2588 :type: int 

2589 

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

2591 :type: Location 

2592 

2593 :attribute element_type: type of the array elements 

2594 :type: Type 

2595 

2596 """ 

2597 

2598 def __init__( 

2599 self, location, element_type, loc_lower, lower_bound, loc_upper, upper_bound 

2600 ): 

2601 # lobster-exclude: Constructor only declares variables 

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

2603 assert isinstance(lower_bound, int) 

2604 assert lower_bound >= 0 

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

2606 assert upper_bound is None or upper_bound >= 0 

2607 assert isinstance(loc_lower, Location) 

2608 assert isinstance(loc_upper, Location) 

2609 

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

2611 name = "universal array" 

2612 elif upper_bound is None: 

2613 if lower_bound == 0: 

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

2615 else: 

2616 name = "array of at least %u %s" % (lower_bound, element_type.name) 

2617 elif lower_bound == upper_bound: 

2618 name = "array of %u %s" % (lower_bound, element_type.name) 

2619 else: 

2620 name = "array of %u to %u %s" % ( 

2621 lower_bound, 

2622 upper_bound, 

2623 element_type.name, 

2624 ) 

2625 super().__init__(name, location) 

2626 self.lower_bound = lower_bound 

2627 self.loc_lower = loc_lower 

2628 self.upper_bound = upper_bound 

2629 self.loc_upper = loc_upper 

2630 self.element_type = element_type 

2631 

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

2633 # lobster-exclude: Debugging feature 

2634 self.write_indent(indent, "Array_Type") 

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

2636 if self.upper_bound is None: 

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

2638 else: 

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

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

2641 

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

2643 assert isinstance(mh, Message_Handler) 

2644 assert isinstance(gstab, Symbol_Table) 

2645 

2646 if isinstance(value, Array_Aggregate): 

2647 return all( 

2648 self.element_type.perform_type_checks(mh, v, gstab) for v in value.value 

2649 ) 

2650 else: 

2651 assert isinstance(value, Implicit_Null) 

2652 return True 

2653 

2654 def get_example_value(self): 

2655 # lobster-exclude: utility method 

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

2657 

2658 

2659class Union_Type(Type): 

2660 # lobster-trace: LRM.union_type 

2661 # lobster-trace: LRM.Union_Type_Minimum_Members 

2662 # lobster-trace: LRM.Union_Type_Record_Types_Only 

2663 """Anonymous union type for record references. 

2664 

2665 These are declared implicitly when a record component specifies 

2666 multiple allowed record types using bracket syntax:: 

2667 

2668 parent [Systemrequirement, Codebeamerrequirement] 

2669 ^ 

2670 

2671 :attribute types: the allowed record types 

2672 :type: list[Record_Type] 

2673 

2674 """ 

2675 

2676 def __init__(self, location, types): 

2677 assert isinstance(types, list) 

2678 assert len(types) >= 1 

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

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

2681 super().__init__(name, location) 

2682 self.types = types 

2683 self._field_map = None 

2684 

2685 def get_field_map(self): 

2686 # lobster-trace: LRM.Union_Type_Field_Access 

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

2688 

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

2690 

2691 * ``component``: a representative Composite_Component 

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

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

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

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

2696 

2697 :rtype: dict[str, dict] 

2698 """ 

2699 if self._field_map is not None: 

2700 return self._field_map 

2701 

2702 field_map = {} 

2703 for record_type in self.types: 

2704 seen_in_type = set() 

2705 for comp in record_type.all_components(): 

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

2707 continue 

2708 seen_in_type.add(comp.name) 

2709 if comp.name not in field_map: 

2710 field_map[comp.name] = { 

2711 "component": comp, 

2712 "n_typ": comp.n_typ, 

2713 "count": 1, 

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

2715 "optional_in_any": comp.optional, 

2716 } 

2717 else: 

2718 info = field_map[comp.name] 

2719 info["count"] += 1 

2720 # Type identity (is) is correct here: 

2721 # non-union type objects are structural 

2722 # singletons in the symbol table, so 

2723 # identity comparison is both correct and 

2724 # cheap. 

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

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

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

2728 info["optional_in_any"] = True 

2729 

2730 self._field_map = field_map 

2731 return self._field_map 

2732 

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

2734 # lobster-exclude: Debugging feature 

2735 self.write_indent(indent, "Union_Type") 

2736 for t in self.types: 

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

2738 

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

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

2741 # happens in Record_Reference.resolve_references() via 

2742 # is_compatible(). Returning True unconditionally is 

2743 # intentional. 

2744 assert isinstance(mh, Message_Handler) 

2745 assert isinstance(value, Expression) 

2746 assert isinstance(gstab, Symbol_Table) 

2747 return True 

2748 

2749 def is_compatible(self, record_type): 

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

2751 

2752 :param record_type: type to check 

2753 :type record_type: Record_Type 

2754 

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

2756 :rtype: bool 

2757 """ 

2758 assert isinstance(record_type, Record_Type) 

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

2760 

2761 def get_example_value(self): 

2762 # lobster-exclude: utility method 

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

2764 

2765 

2766class Builtin_Integer(Builtin_Numeric_Type): 

2767 # lobster-trace: LRM.Builtin_Types 

2768 # lobster-trace: LRM.Integer_Values 

2769 """Builtin integer type.""" 

2770 

2771 def __init__(self): 

2772 super().__init__("Integer") 

2773 

2774 def get_example_value(self): 

2775 # lobster-exclude: utility method 

2776 return "100" 

2777 

2778 

2779class Builtin_Decimal(Builtin_Numeric_Type): 

2780 # lobster-trace: LRM.Builtin_Types 

2781 # lobster-trace: LRM.Decimal_Values 

2782 """Builtin decimal type.""" 

2783 

2784 def __init__(self): 

2785 super().__init__("Decimal") 

2786 

2787 def get_example_value(self): 

2788 # lobster-exclude: utility method 

2789 return "3.14" 

2790 

2791 

2792class Builtin_Boolean(Builtin_Type): 

2793 # lobster-trace: LRM.Builtin_Types 

2794 # lobster-trace: LRM.Boolean_Values 

2795 """Builtin boolean type.""" 

2796 

2797 def __init__(self): 

2798 super().__init__("Boolean") 

2799 

2800 def get_example_value(self): 

2801 # lobster-exclude: utility method 

2802 return "true" 

2803 

2804 

2805class Builtin_String(Builtin_Type): 

2806 # lobster-trace: LRM.Builtin_Types 

2807 # lobster-trace: LRM.String_Values 

2808 """Builtin string type.""" 

2809 

2810 def __init__(self): 

2811 super().__init__("String") 

2812 

2813 def get_example_value(self): 

2814 # lobster-exclude: utility method 

2815 return '"potato"' 

2816 

2817 

2818class Builtin_Markup_String(Builtin_String): 

2819 # lobster-trace: LRM.Builtin_Types 

2820 # lobster-trace: LRM.Markup_String_Values 

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

2822 objects. 

2823 """ 

2824 

2825 def __init__(self): 

2826 super().__init__() 

2827 self.name = "Markup_String" 

2828 

2829 def get_example_value(self): 

2830 # lobster-exclude: utility method 

2831 return '"also see [[potato]]"' 

2832 

2833 

2834class Package(Entity): 

2835 """Packages. 

2836 

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

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

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

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

2841 

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

2843 trlc file 

2844 :type: bool 

2845 

2846 :attribute symbols: symbol table of the package 

2847 :type: Symbol_Table 

2848 

2849 """ 

2850 

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

2852 # lobster-exclude: Constructor only declares variables 

2853 super().__init__(name, location) 

2854 assert isinstance(builtin_stab, Symbol_Table) 

2855 assert isinstance(declared_late, bool) 

2856 self.symbols = Symbol_Table() 

2857 self.symbols.make_visible(builtin_stab) 

2858 self.declared_late = declared_late 

2859 

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

2861 # lobster-exclude: Debugging feature 

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

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

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

2865 

2866 def __repr__(self): 

2867 return "%s<%s>" % (self.__class__.__name__, self.name) 

2868 

2869 

2870class Composite_Type(Concrete_Type, metaclass=ABCMeta): 

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

2872 functionality. 

2873 

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

2875 :type: Symbol_Table[Composite_Component] 

2876 

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

2878 :type: str 

2879 

2880 :attribute checks: user-defined checks for this type (excluding \ 

2881 inherited checks) 

2882 :type: list[Check] 

2883 

2884 """ 

2885 

2886 def __init__(self, name, description, location, package, inherited_symbols=None): 

2887 # lobster-trace: LRM.Described_Name_Description 

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

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

2890 assert isinstance(inherited_symbols, Symbol_Table) or inherited_symbols is None 

2891 

2892 self.components = Symbol_Table(inherited_symbols) 

2893 self.description = description 

2894 self.checks = [] 

2895 

2896 def add_check(self, n_check): 

2897 # lobster-trace: LRM.Check_Evaluation_Order 

2898 assert isinstance(n_check, Check) 

2899 self.checks.append(n_check) 

2900 

2901 def iter_checks(self): 

2902 # lobster-trace: LRM.Check_Evaluation_Order 

2903 yield from self.checks 

2904 

2905 def all_components(self): 

2906 # lobster-exclude: Convenience function 

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

2908 

2909 :rtype: list[Composite_Component] 

2910 """ 

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

2912 

2913 

2914class Composite_Component(Typed_Entity): 

2915 """Component in a record or tuple. 

2916 

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

2918 declared:: 

2919 

2920 type|tuple T { 

2921 foo "blah" optional Boolean 

2922 ^1 ^2 ^3 ^4 

2923 

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

2925 :type: str 

2926 

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

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

2929 :type: Composite_Type 

2930 

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

2932 :type: bool 

2933 

2934 """ 

2935 

2936 def __init__(self, name, description, location, member_of, n_typ, optional): 

2937 # lobster-trace: LRM.Described_Name_Description 

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

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

2940 assert isinstance(member_of, Composite_Type) 

2941 assert isinstance(optional, bool) 

2942 self.description = description 

2943 self.member_of = member_of 

2944 self.optional = optional 

2945 

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

2947 # lobster-exclude: Debugging feature 

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

2949 if self.description: 

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

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

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

2953 

2954 def __repr__(self): 

2955 return "%s<%s>" % ( 

2956 self.__class__.__name__, 

2957 self.member_of.fully_qualified_name() + "." + self.name, 

2958 ) 

2959 

2960 

2961class Record_Type(Composite_Type): 

2962 """A user-defined record type. 

2963 

2964 In this example:: 

2965 

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

2967 ^1 ^2 ^3 

2968 

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

2970 of the :class:`Composite_Type` base. 

2971 

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

2973 :type: Record_Type 

2974 

2975 :attribute frozen: mapping of frozen components 

2976 :type: dict[str, Expression] 

2977 

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

2979 :type: bool 

2980 

2981 :attribute is_abstract: type is abstract 

2982 :type: bool 

2983 

2984 """ 

2985 

2986 def __init__(self, name, description, location, package, n_parent, is_abstract): 

2987 # lobster-exclude: Constructor only declares variables 

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

2989 assert isinstance(is_abstract, bool) 

2990 super().__init__( 

2991 name, 

2992 description, 

2993 location, 

2994 package, 

2995 n_parent.components if n_parent else None, 

2996 ) 

2997 self.parent = n_parent 

2998 self.frozen = {} 

2999 self.is_final = n_parent.is_final if n_parent else False 

3000 self.is_abstract = is_abstract 

3001 

3002 def iter_checks(self): 

3003 # lobster-trace: LRM.Check_Evaluation_Order 

3004 # lobster-trace: LRM.Check_Evaluation_Order_For_Extensions 

3005 if self.parent: 

3006 yield from self.parent.iter_checks() 

3007 yield from self.checks 

3008 

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

3010 # lobster-exclude: Debugging feature 

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

3012 if self.description: 

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

3014 if self.parent: 

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

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

3017 if self.checks: 

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

3019 for n_check in self.checks: 

3020 n_check.dump(indent + 2) 

3021 else: 

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

3023 

3024 def all_components(self): 

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

3026 

3027 :rtype: list[Composite_Component] 

3028 """ 

3029 if self.parent: 

3030 return self.parent.all_components() + list(self.components.table.values()) 

3031 else: 

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

3033 

3034 def is_subclass_of(self, record_type): 

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

3036 

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

3038 :type record_type: Record_Type 

3039 

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

3041 :rtype: Boolean 

3042 """ 

3043 assert isinstance(record_type, Record_Type) 

3044 

3045 ptr = self 

3046 while ptr: 

3047 if ptr is record_type: 

3048 return True 

3049 else: 

3050 ptr = ptr.parent 

3051 return False 

3052 

3053 def is_frozen(self, n_component): 

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

3055 

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

3057 (or any of its parents) 

3058 :type n_component: Composite_Component 

3059 

3060 :rtype: bool 

3061 """ 

3062 assert isinstance(n_component, Composite_Component) 

3063 if n_component.name in self.frozen: 

3064 return True 

3065 elif self.parent: 

3066 return self.parent.is_frozen(n_component) 

3067 else: 

3068 return False 

3069 

3070 def get_freezing_expression(self, n_component): 

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

3072 

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

3074 component that his not frozen. 

3075 

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

3077 (or any of its parents) 

3078 :type n_component: Composite_Component 

3079 

3080 :rtype: Expression 

3081 

3082 """ 

3083 assert isinstance(n_component, Composite_Component) 

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

3085 return self.frozen[n_component.name] 

3086 elif self.parent: 

3087 return self.parent.get_freezing_expression(n_component) 

3088 else: 

3089 assert False 

3090 

3091 def get_example_value(self): 

3092 # lobster-exclude: utility method 

3093 return "%s_instance" % self.name 

3094 

3095 

3096class Tuple_Type(Composite_Type): 

3097 """A user-defined tuple type. 

3098 

3099 In this example:: 

3100 

3101 tuple T "optional description of T" { 

3102 ^1 ^2 

3103 

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

3105 of the :class:`Composite_Type` base. 

3106 

3107 :attribute separators: list of syntactic separators. 

3108 :type: list[Separator] 

3109 

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

3111 precisely one less separator than components. 

3112 

3113 """ 

3114 

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

3116 # lobster-trace: LRM.Tuple_Declaration 

3117 super().__init__(name, description, location, package) 

3118 self.separators = [] 

3119 

3120 def add_separator(self, n_separator): 

3121 # lobster-exclude: utility method 

3122 assert isinstance(n_separator, Separator) 

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

3124 self.separators.append(n_separator) 

3125 

3126 def iter_separators(self): 

3127 """Iterate over all separators""" 

3128 # lobster-exclude: utility method 

3129 yield from self.separators 

3130 

3131 def iter_sequence(self): 

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

3133 # lobster-exclude: utility method 

3134 if self.separators: 

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

3136 yield n_component 

3137 if i < len(self.separators): 

3138 yield self.separators[i] 

3139 else: 

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

3141 

3142 def has_separators(self): 

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

3144 # lobster-exclude: utility method 

3145 return bool(self.separators) 

3146 

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

3148 # lobster-exclude: Debugging feature 

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

3150 if self.description: 

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

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

3153 for n_item in self.iter_sequence(): 

3154 n_item.dump(indent + 2) 

3155 if self.checks: 

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

3157 for n_check in self.checks: 

3158 n_check.dump(indent + 2) 

3159 else: 

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

3161 

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

3163 # lobster-trace: LRM.Check_Evaluation_Order 

3164 assert isinstance(mh, Message_Handler) 

3165 assert isinstance(gstab, Symbol_Table) 

3166 

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

3168 ok = True 

3169 for check in self.iter_checks(): 

3170 if not check.perform(mh, value, gstab): 

3171 ok = False 

3172 return ok 

3173 else: 

3174 assert isinstance(value, Implicit_Null) 

3175 return True 

3176 

3177 def get_example_value(self): 

3178 # lobster-exclude: utility method 

3179 parts = [] 

3180 for n_item in self.iter_sequence(): 

3181 if isinstance(n_item, Composite_Component): 

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

3183 else: 

3184 parts.append(n_item.to_string()) 

3185 if self.has_separators(): 

3186 return " ".join(parts) 

3187 else: 

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

3189 

3190 

3191class Separator(Node): 

3192 # lobster-trace: LRM.Tuple_Declaration 

3193 """User-defined syntactic separator 

3194 

3195 For example:: 

3196 

3197 separator x 

3198 ^1 

3199 

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

3201 :type: Token 

3202 """ 

3203 

3204 def __init__(self, token): 

3205 super().__init__(token.location) 

3206 assert isinstance(token, Token) and token.kind in ( 

3207 "IDENTIFIER", 

3208 "AT", 

3209 "COLON", 

3210 "SEMICOLON", 

3211 ) 

3212 self.token = token 

3213 

3214 def to_string(self): 

3215 return {"AT": "@", "COLON": ":", "SEMICOLON": ";"}.get( 

3216 self.token.kind, self.token.value 

3217 ) 

3218 

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

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

3221 

3222 

3223class Enumeration_Type(Concrete_Type): 

3224 """User-defined enumeration types. 

3225 

3226 For example:: 

3227 

3228 enum T "potato" { 

3229 ^1 ^2 

3230 

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

3232 :type: str 

3233 

3234 :attribute literals: the literals in this enumeration 

3235 :type: Symbol_Table[Enumeration_Literal_Spec] 

3236 

3237 """ 

3238 

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

3240 # lobster-trace: LRM.Described_Name_Description 

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

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

3243 self.literals = Symbol_Table() 

3244 self.description = description 

3245 

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

3247 # lobster-exclude: Debugging feature 

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

3249 if self.description: 

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

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

3252 

3253 def get_example_value(self): 

3254 # lobster-exclude: utility method 

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

3256 if options: 

3257 choice = len(options) // 2 

3258 return self.name + "." + choice.name 

3259 else: 

3260 return "ERROR" 

3261 

3262 

3263class Enumeration_Literal_Spec(Typed_Entity): 

3264 """Declared literal in an enumeration declaration. 

3265 

3266 Note that for literals mentioned later in record object 

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

3268 are used here:: 

3269 

3270 enum ASIL { 

3271 QM "not safety related" 

3272 ^1 ^2 

3273 

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

3275 :type: str 

3276 

3277 """ 

3278 

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

3280 # lobster-trace: LRM.Described_Name_Description 

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

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

3283 assert isinstance(enum, Enumeration_Type) 

3284 self.description = description 

3285 

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

3287 # lobster-exclude: Debugging feature 

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

3289 if self.description: 

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

3291 

3292 

3293class Record_Object(Typed_Entity): 

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

3295 

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

3297 

3298 section "Potato" { 

3299 ^5 

3300 Requirement PotatoReq { 

3301 ^1 ^2 

3302 component1 = 42 

3303 ^3 ^4 

3304 

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

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

3307 class. 

3308 

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

3310 :type: dict[str, Expression] 

3311 

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

3313 :type: Section 

3314 

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

3316 :type: Section 

3317 

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

3319 to: 

3320 

3321 * :class:`Literal` 

3322 * :class:`Unary_Expression` 

3323 * :class:`Array_Aggregate` 

3324 * :class:`Tuple_Aggregate` 

3325 * :class:`Record_Reference` 

3326 * :class:`Implicit_Null` 

3327 

3328 """ 

3329 

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

3331 # lobster-trace: LRM.Section_Declaration 

3332 # lobster-trace: LRM.Unspecified_Optional_Components 

3333 # lobster-trace: LRM.Record_Object_Declaration 

3334 

3335 assert isinstance(n_typ, Record_Type) 

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

3337 assert isinstance(n_package, Package) 

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

3339 self.field = { 

3340 comp.name: Implicit_Null(self, comp) for comp in self.n_typ.all_components() 

3341 } 

3342 self.section = section 

3343 self.n_package = n_package 

3344 

3345 def fully_qualified_name(self): 

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

3347 

3348 :returns: the object's full name 

3349 :rtype: str 

3350 """ 

3351 return self.n_package.name + "." + self.name 

3352 

3353 def to_python_dict(self): 

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

3355 

3356 For example it might provide:: 

3357 

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

3359 "bar" : None, 

3360 "baz" : "value"} 

3361 

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

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

3364 

3365 """ 

3366 return {name: value.to_python_object() for name, value in self.field.items()} 

3367 

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

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

3370 

3371 def assign(self, component, value): 

3372 assert isinstance(component, Composite_Component) 

3373 assert isinstance( 

3374 value, 

3375 ( 

3376 Literal, 

3377 Array_Aggregate, 

3378 Tuple_Aggregate, 

3379 Record_Reference, 

3380 Implicit_Null, 

3381 Unary_Expression, 

3382 ), 

3383 ), "value is %s" % value.__class__.__name__ 

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

3385 raise KeyError( 

3386 f"Component {component.name} already \ 

3387 assigned to {self.n_typ.name} {self.name}!" 

3388 ) 

3389 self.field[component.name] = value 

3390 

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

3392 # lobster-exclude: Debugging feature 

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

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

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

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

3397 value.dump(indent + 2) 

3398 if self.section: 

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

3400 

3401 def resolve_references(self, mh): 

3402 assert isinstance(mh, Message_Handler) 

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

3404 val.resolve_references(mh) 

3405 

3406 def perform_checks(self, mh, gstab): 

3407 # lobster-trace: LRM.Check_Evaluation_Order 

3408 # lobster-trace: LRM.Evaluation_Of_Checks 

3409 assert isinstance(mh, Message_Handler) 

3410 assert isinstance(gstab, Symbol_Table) 

3411 

3412 ok = True 

3413 

3414 # First evaluate all tuple checks 

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

3416 if not n_comp.n_typ.perform_type_checks(mh, self.field[n_comp.name], gstab): 

3417 ok = False 

3418 

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

3420 

3421 # Then evaluate all record checks 

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

3423 # Prints messages, if applicable. Raises exception on 

3424 # fatal checks, which causes this to abort. 

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

3426 ok = False 

3427 

3428 return ok 

3429 

3430 def __repr__(self): 

3431 return "%s<%s>" % ( 

3432 self.__class__.__name__, 

3433 self.n_package.name + "." + self.n_typ.name + "." + self.name, 

3434 ) 

3435 

3436 

3437class Section(Entity): 

3438 # lobster-trace: LRM.Section_Declaration 

3439 """A section for readability 

3440 

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

3442 objects together:: 

3443 

3444 section "Foo" { 

3445 ^^^^^ parent section 

3446 section "Bar" { 

3447 ^^^^^ section 

3448 

3449 :attribute parent: the parent section or None 

3450 :type: Section 

3451 

3452 """ 

3453 

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

3455 super().__init__(name, location) 

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

3457 self.parent = parent 

3458 

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

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

3461 if self.parent is None: 

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

3463 else: 

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

3465 

3466 

3467############################################################################## 

3468# Symbol Table & Scopes 

3469############################################################################## 

3470 

3471 

3472class Symbol_Table: 

3473 """Symbol table mapping names to entities""" 

3474 

3475 def __init__(self, parent=None): 

3476 # lobster-exclude: Constructor only declares variables 

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

3478 self.parent = parent 

3479 self.imported = [] 

3480 self.table = OrderedDict() 

3481 self.trlc_files = [] 

3482 self.section_names = [] 

3483 

3484 @staticmethod 

3485 def simplified_name(name): 

3486 # lobster-trace: LRM.Sufficiently_Distinct 

3487 assert isinstance(name, str) 

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

3489 

3490 def all_names(self): 

3491 # lobster-exclude: API for users 

3492 """All names in the symbol table 

3493 

3494 :rtype: set[str] 

3495 """ 

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

3497 if self.parent: 

3498 rv |= self.parent.all_names() 

3499 return rv 

3500 

3501 def iter_record_objects_by_section(self): 

3502 """API for users 

3503 

3504 Retriving information about the section hierarchy for record objects 

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

3506 sub sections and record objects 

3507 Output: Information about sections and level of sections, 

3508 record objects and levels of record object 

3509 """ 

3510 for record_object in self.iter_record_objects(): 

3511 location = record_object.location.file_name 

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

3513 self.trlc_files.append(location) 

3514 yield location 

3515 if record_object.section: 

3516 object_level = len(record_object.section) - 1 

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

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

3519 self.section_names.append(section) 

3520 yield section.name, level 

3521 yield record_object, object_level 

3522 else: 

3523 object_level = 0 

3524 yield record_object, object_level 

3525 

3526 def iter_record_objects(self): 

3527 # lobster-exclude: API for users 

3528 """Iterate over all record objects 

3529 

3530 :rtype: iterable[Record_Object] 

3531 """ 

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

3533 if isinstance(item, Package): 

3534 yield from item.symbols.iter_record_objects() 

3535 

3536 elif isinstance(item, Record_Object): 

3537 yield item 

3538 

3539 def values(self, subtype=None): 

3540 # lobster-exclude: API for users 

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

3542 if self.parent: 

3543 yield from self.parent.values(subtype) 

3544 for name in sorted(self.table): 

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

3546 yield self.table[name] 

3547 

3548 def make_visible(self, stab): 

3549 assert isinstance(stab, Symbol_Table) 

3550 self.imported.append(stab) 

3551 

3552 def register(self, mh, entity): 

3553 # lobster-trace: LRM.Duplicate_Types 

3554 # lobster-trace: LRM.Unique_Enumeration_Literals 

3555 # lobster-trace: LRM.Tuple_Unique_Field_Names 

3556 # lobster-trace: LRM.Sufficiently_Distinct 

3557 # lobster-trace: LRM.Unique_Object_Names 

3558 

3559 assert isinstance(mh, Message_Handler) 

3560 assert isinstance(entity, Entity) 

3561 

3562 simple_name = self.simplified_name(entity.name) 

3563 

3564 if self.contains_raw(simple_name): 

3565 pdef = self.lookup_direct(mh, entity.name, entity.location, simplified=True) 

3566 if pdef.name == entity.name: 

3567 mh.error( 

3568 entity.location, 

3569 "duplicate definition, previous definition at %s" 

3570 % mh.cross_file_reference(pdef.location), 

3571 ) 

3572 else: 

3573 mh.error( 

3574 entity.location, 

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

3576 % (entity.name, pdef.name, mh.cross_file_reference(pdef.location)), 

3577 ) 

3578 

3579 else: 

3580 self.table[simple_name] = entity 

3581 

3582 def __contains__(self, name): 

3583 # lobster-trace: LRM.Described_Name_Equality 

3584 return self.contains(name) 

3585 

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

3587 # lobster-trace: LRM.Described_Name_Equality 

3588 # lobster-trace: LRM.Sufficiently_Distinct 

3589 # 

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

3591 # table. 

3592 assert isinstance(simple_name, str) 

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

3594 

3595 if simple_name in self.table: 

3596 # No need to continue searching since registering a 

3597 # clashing name would have been stopped 

3598 return precise_name is None or self.table[simple_name].name == precise_name 

3599 

3600 elif self.parent: 

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

3602 

3603 for stab in self.imported: 

3604 if stab.contains_raw(simple_name, precise_name): 

3605 return True 

3606 

3607 return False 

3608 

3609 def contains(self, name): 

3610 # lobster-trace: LRM.Described_Name_Equality 

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

3612 

3613 :param name: the name to test 

3614 :type name: str 

3615 

3616 :rtype: bool 

3617 """ 

3618 assert isinstance(name, str) 

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

3620 

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

3622 # lobster-trace: LRM.Described_Name_Equality 

3623 # lobster-trace: LRM.Sufficiently_Distinct 

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

3625 

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

3627 e.g. find some user-defined types you know are there. 

3628 

3629 :param mh: The message handler to use 

3630 :type mh: Message_Handler 

3631 

3632 :param name: The name to search for 

3633 :type name: str 

3634 

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

3636 is not an instance of the given class 

3637 :type required_subclass: type 

3638 

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

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

3641 :rtype: Entity 

3642 

3643 """ 

3644 assert isinstance(mh, Message_Handler) 

3645 assert isinstance(name, str) 

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

3647 

3648 simple_name = self.simplified_name(name) 

3649 

3650 ptr = self 

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

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

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

3654 rv = ptr.table[simple_name] 

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

3656 return None 

3657 

3658 if required_subclass is not None and not isinstance( 3658 ↛ 3661line 3658 didn't jump to line 3661 because the condition on line 3658 was never true

3659 rv, required_subclass 

3660 ): 

3661 mh.error( 

3662 rv.location, 

3663 "%s %s is not a %s" 

3664 % (rv.__class__.__name__, name, required_subclass.__name__), 

3665 ) 

3666 return rv 

3667 else: 

3668 ptr = ptr.parent 

3669 

3670 return None 

3671 

3672 def lookup_direct( 

3673 self, mh, name, error_location, required_subclass=None, simplified=False 

3674 ): 

3675 # lobster-trace: LRM.Described_Name_Equality 

3676 # lobster-trace: LRM.Sufficiently_Distinct 

3677 # lobster-trace: LRM.Valid_Base_Names 

3678 # lobster-trace: LRM.Valid_Access_Prefixes 

3679 # lobster-trace: LRM.Valid_Function_Prefixes 

3680 """Retrieve an object from the table 

3681 

3682 For example:: 

3683 

3684 pkg = stab.lookup_direct(mh, 

3685 "potato", 

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

3687 Package) 

3688 

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

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

3691 Package, then the following error is issued:: 

3692 

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

3694 

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

3696 

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

3698 

3699 :param mh: The message handler to use 

3700 :type mh: Message_Handler 

3701 

3702 :param name: The name to search for 

3703 :type name: str 

3704 

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

3706 not found 

3707 :type error_location: Location 

3708 

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

3710 is not an instance of the given class 

3711 :type required_subclass: type 

3712 

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

3714 of the actual name 

3715 :type simplified: bool 

3716 

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

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

3719 :returns: the specified entity 

3720 :rtype: Entity 

3721 

3722 """ 

3723 assert isinstance(mh, Message_Handler) 

3724 assert isinstance(name, str) 

3725 assert isinstance(error_location, Location) 

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

3727 assert isinstance(simplified, bool) 

3728 

3729 simple_name = self.simplified_name(name) 

3730 ptr = self 

3731 options = [] 

3732 

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

3734 while ptr: 

3735 if simple_name in ptr.table: 

3736 rv = ptr.table[simple_name] 

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

3738 mh.error( 

3739 error_location, 

3740 "unknown symbol %s, did you mean %s?" % (name, rv.name), 

3741 ) 

3742 

3743 if required_subclass is not None and not isinstance( 

3744 rv, required_subclass 

3745 ): 

3746 mh.error( 

3747 error_location, 

3748 "%s %s is not a %s" 

3749 % (rv.__class__.__name__, name, required_subclass.__name__), 

3750 ) 

3751 return rv 

3752 else: 

3753 options += list(item.name for item in ptr.table.values()) 

3754 ptr = ptr.parent 

3755 

3756 matches = get_close_matches(word=name, possibilities=options, n=1) 

3757 

3758 if matches: 

3759 mh.error( 

3760 error_location, 

3761 "unknown symbol %s, did you mean %s?" % (name, matches[0]), 

3762 ) 

3763 else: 

3764 mh.error(error_location, "unknown symbol %s" % name) 

3765 

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

3767 # lobster-trace: LRM.Described_Name_Equality 

3768 assert isinstance(mh, Message_Handler) 

3769 assert isinstance(referencing_token, Token) 

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

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

3772 

3773 return self.lookup_direct( 

3774 mh=mh, 

3775 name=referencing_token.value, 

3776 error_location=referencing_token.location, 

3777 required_subclass=required_subclass, 

3778 ) 

3779 

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

3781 # lobster-exclude: Debugging feature 

3782 assert isinstance(indent, int) 

3783 assert indent >= 0 

3784 assert isinstance(message, str) 

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

3786 

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

3788 # lobster-exclude: Debugging feature 

3789 if omit_heading: 

3790 new_indent = indent 

3791 else: 

3792 self.write_indent(indent, "Symbol_Table") 

3793 new_indent = indent + 1 

3794 ptr = self 

3795 while ptr: 

3796 for name in ptr.table: 

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

3798 ptr = ptr.parent 

3799 

3800 @classmethod 

3801 def create_global_table(cls, mh): 

3802 # lobster-trace: LRM.Builtin_Types 

3803 # lobster-trace: LRM.Builtin_Functions 

3804 # lobster-trace: LRM.Builtin_Type_Conversion_Functions 

3805 # lobster-trace: LRM.Signature_Len 

3806 # lobster-trace: LRM.Signature_String_End_Functions 

3807 # lobster-trace: LRM.Signature_Matches 

3808 

3809 stab = Symbol_Table() 

3810 stab.register(mh, Builtin_Integer()) 

3811 stab.register(mh, Builtin_Decimal()) 

3812 stab.register(mh, Builtin_Boolean()) 

3813 stab.register(mh, Builtin_String()) 

3814 stab.register(mh, Builtin_Markup_String()) 

3815 stab.register(mh, Builtin_Function("len", 1)) 

3816 stab.register(mh, Builtin_Function("startswith", 2)) 

3817 stab.register(mh, Builtin_Function("endswith", 2)) 

3818 stab.register(mh, Builtin_Function("matches", 2)) 

3819 stab.register(mh, Builtin_Function("oneof", 1, arity_at_least=True)) 

3820 

3821 return stab 

3822 

3823 

3824class Scope: 

3825 def __init__(self): 

3826 # lobster-exclude: Constructor only declares variables 

3827 self.scope = [] 

3828 

3829 def push(self, stab): 

3830 assert isinstance(stab, Symbol_Table) 

3831 self.scope.append(stab) 

3832 

3833 def pop(self): 

3834 self.scope.pop() 

3835 

3836 def contains(self, name): 

3837 assert isinstance(name, str) 

3838 

3839 for stab in reversed(self.scope): 

3840 if stab.contains(name): 

3841 return True 

3842 return False 

3843 

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

3845 assert len(self.scope) >= 1 

3846 assert isinstance(mh, Message_Handler) 

3847 assert isinstance(referencing_token, Token) 

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

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

3850 

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

3852 if stab.contains(referencing_token.value): 

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

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

3855 

3856 def size(self): 

3857 return len(self.scope)