Coverage for trlc/ast.py: 90%

1317 statements  

« prev     ^ index     » next       coverage.py v7.10.7, created at 2026-06-18 05:43 +0000

1#!/usr/bin/env python3 

2# 

3# TRLC - Treat Requirements Like Code 

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

5# Copyright (C) 2024-2025 Florian Schanda 

6# 

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

8# 

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

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

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

12# (at your option) any later version. 

13# 

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

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

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

17# License for more details. 

18# 

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

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

21 

22from abc import ABCMeta, abstractmethod 

23import re 

24 

25from copy import copy 

26from difflib import get_close_matches 

27from enum import Enum, auto 

28from collections import OrderedDict 

29from fractions import Fraction 

30 

31from trlc.errors import TRLC_Error, Location, Message_Handler 

32from trlc.lexer import Token 

33from trlc import math 

34 

35# 

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

37# reference implementation. There are four sections: 

38# 

39# - Valuations deal with concrete values for record objects 

40# - AST expressions deal with the syntax tree 

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

42# - Symbol_Table and scope deals with name resolution 

43# 

44 

45 

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

47# Valuations 

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

49 

50class Value: 

51 # lobster-trace: LRM.Boolean_Values 

52 # lobster-trace: LRM.Integer_Values 

53 # lobster-trace: LRM.Decimal_Values 

54 # lobster-trace: LRM.String_Values 

55 # lobster-trace: LRM.Markup_String_Values 

56 """Polymorphic value for evaluating expressions. 

57 

58 Any record references will be fully resolved. 

59 

60 :attribute location: source location this value comes from 

61 :type: Location 

62 

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

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

65 Record_Reference, Enumeration_Literal_Spec 

66 

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

68 :type: Type 

69 """ 

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

71 assert isinstance(location, Location) 

72 assert value is None or \ 

73 isinstance(value, (str, 

74 int, 

75 bool, 

76 list, # for arrays 

77 dict, # for tuples 

78 Fraction, 

79 Record_Reference, 

80 Enumeration_Literal_Spec)) 

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

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

83 

84 self.location = location 

85 self.value = value 

86 self.typ = typ 

87 

88 def __eq__(self, other): 

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

90 

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

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

93 

94 def resolve_references(self, mh): 

95 assert isinstance(mh, Message_Handler) 

96 

97 if isinstance(self.value, Record_Reference): 

98 self.value.resolve(mh) 

99 

100 

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

102# AST Nodes 

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

104 

105class Node(metaclass=ABCMeta): 

106 """Base class for all AST items. 

107 

108 :attribute location: source location 

109 :type: Location 

110 """ 

111 def __init__(self, location): 

112 # lobster-exclude: Constructor only declares variables 

113 assert isinstance(location, Location) 

114 self.location = location 

115 

116 def set_ast_link(self, tok): 

117 assert isinstance(tok, Token) 

118 tok.ast_link = self 

119 

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

121 # lobster-exclude: Debugging feature 

122 assert isinstance(indent, int) 

123 assert indent >= 0 

124 assert isinstance(message, str) 

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

126 

127 @abstractmethod 

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

129 """Visualise the parse tree. 

130 

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

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

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

134 output like this:: 

135 

136 Symbol_Table 

137 Builtin_Boolean 

138 Builtin_Integer 

139 Builtin_Decimal 

140 Builtin_String 

141 Builtin_Markup_String 

142 Package bar 

143 Symbol_Table 

144 Record_Type MyType 

145 Composite_Component name 

146 Optional: False 

147 Type: String 

148 Checks 

149 Error 'description is too short' 

150 Anchor: description 

151 Binary Binary_Operator.COMP_GT Expression 

152 Type: Boolean 

153 Unary Unary_Operator.STRING_LENGTH Expression 

154 Type: Integer 

155 Name Reference to description 

156 Integer Literal 10 

157 Package instances 

158 Symbol_Table 

159 Record_Object SomeThing 

160 Type: MyType 

161 Field description: "Potato" 

162 Builtin_Function endswith 

163 Builtin_Function len 

164 Builtin_Function matches 

165 Builtin_Function startswith 

166 Builtin_Function oneof 

167 

168 """ 

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

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

171 # lobster-exclude: Debugging feature 

172 

173 

174class Check_Block(Node): 

175 """Node representing check blocks 

176 

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

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

179 

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

181 :type: Composite_Type 

182 

183 :attribute checks: list of checks 

184 :type: list[Check] 

185 

186 """ 

187 def __init__(self, location, n_typ): 

188 # lobster-trace: LRM.Check_Block 

189 super().__init__(location) 

190 assert isinstance(n_typ, Composite_Type) 

191 self.n_typ = n_typ 

192 self.checks = [] 

193 

194 def add_check(self, n_check): 

195 # lobster-trace: LRM.Check_Evaluation_Order 

196 assert isinstance(n_check, Check) 

197 self.checks.append(n_check) 

198 

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

200 # lobster-exclude: Debugging feature 

201 self.write_indent(indent, "Check_Block") 

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

203 for n_check in self.checks: 

204 n_check.dump(indent + 1) 

205 

206 

207class Compilation_Unit(Node): 

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

209 

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

211 :type: Package 

212 

213 :attribute imports: package imported by this file 

214 :type: list[Package] 

215 

216 :attribute items: list of 

217 :type: list[Node] 

218 

219 """ 

220 def __init__(self, file_name): 

221 # lobster-exclude: Constructor only declares variables 

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

223 self.package = None 

224 self.imports = None 

225 self.raw_imports = [] 

226 self.items = [] 

227 

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

229 # lobster-exclude: Debugging feature 

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

231 for t_import in self.raw_imports: 

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

233 for n_item in self.items: 

234 n_item.dump(indent + 1) 

235 

236 def set_package(self, pkg): 

237 # lobster-trace: LRM.Current_Package 

238 assert isinstance(pkg, Package) 

239 self.package = pkg 

240 

241 def add_import(self, mh, t_import): 

242 # lobster-trace: LRM.Import_Visibility 

243 # lobster-trace: LRM.Self_Imports 

244 assert isinstance(mh, Message_Handler) 

245 assert isinstance(t_import, Token) 

246 assert t_import.kind == "IDENTIFIER" 

247 

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

249 mh.error(t_import.location, 

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

251 

252 # Skip duplicates 

253 for t_previous in self.raw_imports: 

254 if t_previous.value == t_import.value: 

255 mh.warning(t_import.location, 

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

257 return 

258 

259 self.raw_imports.append(t_import) 

260 

261 def resolve_imports(self, mh, stab): 

262 # lobster-trace: LRM.Import_Visibility 

263 assert isinstance(mh, Message_Handler) 

264 assert isinstance(stab, Symbol_Table) 

265 self.imports = set() 

266 for t_import in self.raw_imports: 

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

268 # generate more error later. 

269 try: 

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

271 self.imports.add(a_import) 

272 a_import.set_ast_link(t_import) 

273 except TRLC_Error: 

274 pass 

275 

276 def is_visible(self, n_pkg): 

277 # lobster-trace: LRM.Import_Visibility 

278 assert self.imports is not None 

279 assert isinstance(n_pkg, Package) 

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

281 

282 def add_item(self, node): 

283 # lobster-trace: LRM.RSL_File 

284 # lobster-trace: LRM.TRLC_File 

285 assert isinstance(node, (Concrete_Type, 

286 Check_Block, 

287 Record_Object)), \ 

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

289 self.items.append(node) 

290 

291 

292class Check(Node): 

293 """User defined check 

294 

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

296 

297 checks T { 

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

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

300 

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

302 :type: Composite_Type 

303 

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

305 :type: Expression 

306 

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

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

309 :type: Composite_Component 

310 

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

312 not specified the default is 'error') 

313 :type: str 

314 

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

316 :type: str 

317 """ 

318 def __init__(self, 

319 n_type, 

320 n_expr, 

321 n_anchor, 

322 severity, 

323 t_message, 

324 extrainfo): 

325 # lobster-trace: LRM.Check_Block 

326 assert isinstance(n_type, Composite_Type) 

327 assert isinstance(n_expr, Expression) 

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

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

330 assert isinstance(t_message, Token) 

331 assert t_message.kind == "STRING" 

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

333 super().__init__(t_message.location) 

334 

335 self.n_type = n_type 

336 self.n_expr = n_expr 

337 self.n_anchor = n_anchor 

338 self.severity = severity 

339 # lobster-trace: LRM.No_Newlines_In_Message 

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

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

342 # raised is non-fatal. 

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

344 self.extrainfo = extrainfo 

345 self._uses_field_access = None 

346 

347 @property 

348 def uses_field_access(self): 

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

350 reference? 

351 

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

353 :class:`Field_Access_Expression` whose prefix has a 

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

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

356 Phase B ("after references"). 

357 

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

359 :rtype: bool 

360 

361 """ 

362 if self._uses_field_access is None: 

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

364 return self._uses_field_access 

365 

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

367 # lobster-exclude: Debugging feature 

368 if self.severity == "warning": 

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

370 elif self.severity == "error": 

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

372 else: 

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

374 if self.n_anchor: 

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

376 self.n_expr.dump(indent + 1) 

377 

378 def get_real_location(self, composite_object): 

379 # lobster-exclude: LRM.Anchoring 

380 assert isinstance(composite_object, (Record_Object, 

381 Tuple_Aggregate)) 

382 if isinstance(composite_object, Record_Object): 

383 fields = composite_object.field 

384 else: 

385 fields = composite_object.value 

386 

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

388 return composite_object.location 

389 else: 

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

391 

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

393 # lobster-trace: LRM.Check_Messages 

394 # lobster-trace: LRM.Check_Severity 

395 assert isinstance(mh, Message_Handler) 

396 assert isinstance(composite_object, (Record_Object, 

397 Tuple_Aggregate)) 

398 assert isinstance(gstab, Symbol_Table) 

399 

400 if isinstance(composite_object, Record_Object): 

401 result = self.n_expr.evaluate(mh, 

402 copy(composite_object.field), 

403 gstab) 

404 else: 

405 result = self.n_expr.evaluate(mh, 

406 copy(composite_object.value), 

407 gstab) 

408 if result.value is None: 

409 loc = self.get_real_location(composite_object) 

410 mh.error(loc, 

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

412 (self.n_expr.to_string(), 

413 mh.cross_file_reference(self.location))) 

414 

415 assert isinstance(result.value, bool) 

416 

417 if not result.value: 

418 loc = self.get_real_location(composite_object) 

419 if self.severity == "warning": 

420 mh.warning(location = loc, 

421 message = self.message, 

422 explanation = self.extrainfo, 

423 user = True) 

424 else: 

425 mh.error(location = loc, 

426 message = self.message, 

427 explanation = self.extrainfo, 

428 fatal = self.severity == "fatal", 

429 user = True) 

430 return False 

431 

432 return True 

433 

434############################################################################## 

435# AST Nodes (Expressions) 

436############################################################################## 

437 

438 

439class Unary_Operator(Enum): 

440 # lobster-exclude: Utility enumeration for unary operators 

441 MINUS = auto() 

442 PLUS = auto() 

443 LOGICAL_NOT = auto() 

444 ABSOLUTE_VALUE = auto() 

445 

446 STRING_LENGTH = auto() 

447 ARRAY_LENGTH = auto() 

448 

449 CONVERSION_TO_INT = auto() 

450 CONVERSION_TO_DECIMAL = auto() 

451 

452 

453class Binary_Operator(Enum): 

454 # lobster-exclude: Utility enumeration for binary operators 

455 LOGICAL_AND = auto() # Short-circuit 

456 LOGICAL_OR = auto() # Short-circuit 

457 LOGICAL_XOR = auto() 

458 LOGICAL_IMPLIES = auto() # Short-circuit 

459 

460 COMP_EQ = auto() 

461 COMP_NEQ = auto() 

462 COMP_LT = auto() 

463 COMP_LEQ = auto() 

464 COMP_GT = auto() 

465 COMP_GEQ = auto() 

466 

467 STRING_CONTAINS = auto() 

468 STRING_STARTSWITH = auto() 

469 STRING_ENDSWITH = auto() 

470 STRING_REGEX = auto() 

471 

472 ARRAY_CONTAINS = auto() 

473 

474 PLUS = auto() 

475 MINUS = auto() 

476 TIMES = auto() 

477 DIVIDE = auto() 

478 REMAINDER = auto() 

479 

480 POWER = auto() 

481 

482 INDEX = auto() 

483 

484 

485class Expression(Node, metaclass=ABCMeta): 

486 """Abstract base class for all expressions. 

487 

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

489 :type: Type 

490 """ 

491 def __init__(self, location, typ): 

492 # lobster-exclude: Constructor only declares variables 

493 super().__init__(location) 

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

495 self.typ = typ 

496 

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

498 """Evaluate the expression in the given context 

499 

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

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

502 dictionary that maps names (such as record fields or 

503 quantified variables) to expressions. 

504 

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

506 evaluations), otherwise it must contain the global symbol 

507 table to resolve record references. 

508 

509 :param mh: the message handler to use 

510 :type mh: Message_Handler 

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

512 :type context: dict[str, Expression] 

513 :raise TRLC_Error: if the expression cannot be evaluated 

514 :return: result of the evaluation 

515 :rtype: Value 

516 

517 """ 

518 assert isinstance(mh, Message_Handler) 

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

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

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

522 self.__class__.__name__ 

523 

524 @abstractmethod 

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

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

527 self.__class__.__name__ 

528 

529 def ensure_type(self, mh, typ): 

530 # lobster-trace: LRM.Restricted_Null 

531 # lobster-trace: LRM.Null_Is_Invalid 

532 

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

534 if self.typ is None: 

535 mh.error(self.location, 

536 "null is not permitted here") 

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

538 mh.error(self.location, 

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

540 (typ.__name__, 

541 self.typ.__class__.__name__)) 

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

543 mh.error(self.location, 

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

545 (typ.name, 

546 self.typ.name)) 

547 

548 def resolve_references(self, mh): 

549 assert isinstance(mh, Message_Handler) 

550 

551 @abstractmethod 

552 def can_be_null(self): 

553 """Test if the expression could return null 

554 

555 Checks the expression if it could generate a null value 

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

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

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

559 occur earlier. 

560 

561 :return: possibility of encountering null 

562 :rtype: bool 

563 

564 """ 

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

566 self.__class__.__name__ 

567 

568 def uses_field_access(self): 

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

570 union reference. 

571 

572 Returns True if any sub-expression is a 

573 :class:`Field_Access_Expression` whose prefix has a 

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

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

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

577 

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

579 :rtype: bool 

580 

581 """ 

582 return False 

583 

584 

585class Implicit_Null(Expression): 

586 """Synthesised null values 

587 

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

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

590 implicit null expression for this. 

591 

592 For example given this TRLC type:: 

593 

594 type T { 

595 x optional Integer 

596 } 

597 

598 And this declaration:: 

599 

600 T Potato {} 

601 

602 Then the field mapping for Potato will be:: 

603 

604 {x: Implicit_Null} 

605 

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

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

608 that can appear in check expressions. 

609 

610 """ 

611 def __init__(self, composite_object, composite_component): 

612 # lobster-trace: LRM.Unspecified_Optional_Components 

613 assert isinstance(composite_object, (Record_Object, 

614 Tuple_Aggregate)) 

615 assert isinstance(composite_component, Composite_Component) 

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

617 

618 def to_string(self): 

619 return "null" 

620 

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

622 # lobster-trace: LRM.Unspecified_Optional_Components 

623 assert isinstance(mh, Message_Handler) 

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

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

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

627 

628 def to_python_object(self): 

629 return None 

630 

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

632 # lobster-exclude: Debugging feature 

633 self.write_indent(indent, "Implicit_Null") 

634 

635 def can_be_null(self): 

636 return True 

637 

638 

639class Literal(Expression, metaclass=ABCMeta): 

640 """Abstract base for all Literals 

641 

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

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

644 check if you are dealing with a literal:: 

645 

646 isinstance(my_expression, Literal) 

647 

648 """ 

649 @abstractmethod 

650 def to_python_object(self): 

651 assert False 

652 

653 

654class Null_Literal(Literal): 

655 # lobster-trace: LRM.Primary 

656 """The null literal 

657 

658 This can appear in check expressions:: 

659 

660 a /= null implies a > 5 

661 ^^^^ 

662 

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

664 values that appear in record objects. 

665 

666 """ 

667 def __init__(self, token): 

668 assert isinstance(token, Token) 

669 assert token.kind == "KEYWORD" 

670 assert token.value == "null" 

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

672 

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

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

675 

676 def to_string(self): 

677 return "null" 

678 

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

680 assert isinstance(mh, Message_Handler) 

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

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

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

684 

685 def to_python_object(self): 

686 return None 

687 

688 def can_be_null(self): 

689 return True 

690 

691 

692class Integer_Literal(Literal): 

693 # lobster-trace: LRM.Integer_Values 

694 # lobster-trace: LRM.Primary 

695 """Integer literals 

696 

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

698 actually a unary negation expression, operating on a positive 

699 integer literal:: 

700 

701 x == -5 

702 

703 This would create the following tree:: 

704 

705 OP_EQUALITY 

706 NAME_REFERENCE x 

707 UNARY_EXPRESSION - 

708 INTEGER_LITERAL 5 

709 

710 :attribute value: the non-negative integer value 

711 :type: int 

712 """ 

713 def __init__(self, token, typ): 

714 assert isinstance(token, Token) 

715 assert token.kind == "INTEGER" 

716 assert isinstance(typ, Builtin_Integer) 

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

718 

719 self.value = token.value 

720 

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

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

723 

724 def to_string(self): 

725 return str(self.value) 

726 

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

728 assert isinstance(mh, Message_Handler) 

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

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

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

732 

733 def to_python_object(self): 

734 return self.value 

735 

736 def can_be_null(self): 

737 return False 

738 

739 

740class Decimal_Literal(Literal): 

741 # lobster-trace: LRM.Decimal_Values 

742 # lobster-trace: LRM.Primary 

743 """Decimal literals 

744 

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

746 actually a unary negation expression, operating on a positive 

747 decimal literal:: 

748 

749 x == -5.0 

750 

751 This would create the following tree:: 

752 

753 OP_EQUALITY 

754 NAME_REFERENCE x 

755 UNARY_EXPRESSION - 

756 DECIMAL_LITERAL 5.0 

757 

758 :attribute value: the non-negative decimal value 

759 :type: fractions.Fraction 

760 """ 

761 def __init__(self, token, typ): 

762 assert isinstance(token, Token) 

763 assert token.kind == "DECIMAL" 

764 assert isinstance(typ, Builtin_Decimal) 

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

766 

767 self.value = token.value 

768 

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

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

771 

772 def to_string(self): 

773 return str(self.value) 

774 

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

776 assert isinstance(mh, Message_Handler) 

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

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

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

780 

781 def to_python_object(self): 

782 return float(self.value) 

783 

784 def can_be_null(self): 

785 return False 

786 

787 

788class String_Literal(Literal): 

789 # lobster-trace: LRM.String_Values 

790 # lobster-trace: LRM.Markup_String_Values 

791 # lobster-trace: LRM.Primary 

792 """String literals 

793 

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

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

796 

797 "foo\\"bar" 

798 

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

800 

801 :attribute value: string content 

802 :type: str 

803 

804 :attribute references: resolved references of a markup string 

805 :type: list[Record_Reference] 

806 

807 """ 

808 def __init__(self, token, typ): 

809 assert isinstance(token, Token) 

810 assert token.kind == "STRING" 

811 assert isinstance(typ, Builtin_String) 

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

813 

814 self.value = token.value 

815 self.has_references = isinstance(typ, Builtin_Markup_String) 

816 self.references = [] 

817 

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

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

820 if self.has_references: 

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

822 for ref in self.references: 

823 ref.dump(indent + 2) 

824 

825 def to_string(self): 

826 return self.value 

827 

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

829 assert isinstance(mh, Message_Handler) 

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

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

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

833 

834 def to_python_object(self): 

835 return self.value 

836 

837 def resolve_references(self, mh): 

838 assert isinstance(mh, Message_Handler) 

839 for ref in self.references: 

840 ref.resolve_references(mh) 

841 

842 def can_be_null(self): 

843 return False 

844 

845 

846class Boolean_Literal(Literal): 

847 # lobster-trace: LRM.Boolean_Values 

848 # lobster-trace: LRM.Primary 

849 """Boolean values 

850 

851 :attribute value: the boolean value 

852 :type: bool 

853 """ 

854 def __init__(self, token, typ): 

855 assert isinstance(token, Token) 

856 assert token.kind == "KEYWORD" 

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

858 assert isinstance(typ, Builtin_Boolean) 

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

860 

861 self.value = token.value == "true" 

862 

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

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

865 

866 def to_string(self): 

867 return str(self.value) 

868 

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

870 assert isinstance(mh, Message_Handler) 

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

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

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

874 

875 def to_python_object(self): 

876 return self.value 

877 

878 def can_be_null(self): 

879 return False 

880 

881 

882class Enumeration_Literal(Literal): 

883 """Enumeration values 

884 

885 Note that this is distinct from 

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

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

888 

889 foo != my_enum.POTATO 

890 ^^^^^^^^^^^^^^ 

891 

892 To get to the string value of the enumeration literal 

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

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

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

896 ``enum_lit.value.n_typ.name``. 

897 

898 :attribute value: enumeration value 

899 :type: Enumeration_Literal_Spec 

900 

901 """ 

902 def __init__(self, location, literal): 

903 # lobster-exclude: Constructor only declares variables 

904 assert isinstance(literal, Enumeration_Literal_Spec) 

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

906 

907 self.value = literal 

908 

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

910 # lobster-exclude: Debugging feature 

911 self.write_indent(indent, 

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

913 

914 def to_string(self): 

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

916 

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

918 assert isinstance(mh, Message_Handler) 

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

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

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

922 

923 def to_python_object(self): 

924 return self.value.name 

925 

926 def can_be_null(self): 

927 return False 

928 

929 

930class Array_Aggregate(Expression): 

931 """Instances of array types 

932 

933 This is created when assigning to array components:: 

934 

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

936 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 

937 

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

939 limited: 

940 

941 * :class:`Literal` 

942 * :class:`Array_Aggregate` 

943 * :class:`Record_Reference` 

944 

945 :attribute value: contents of the array 

946 :type: list[Expression] 

947 

948 """ 

949 def __init__(self, location, typ): 

950 # lobster-trace: LRM.Record_Object_Declaration 

951 

952 super().__init__(location, typ) 

953 self.value = [] 

954 

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

956 # lobster-exclude: Debugging feature 

957 self.write_indent(indent, "Array_Aggregate") 

958 for n_value in self.value: 

959 n_value.dump(indent + 1) 

960 

961 def append(self, value): 

962 assert isinstance(value, (Literal, 

963 Unary_Expression, 

964 Array_Aggregate, 

965 Tuple_Aggregate, 

966 Record_Reference)) 

967 self.value.append(value) 

968 

969 def to_string(self): 

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

971 

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

973 assert isinstance(mh, Message_Handler) 

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

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

976 return Value(self.location, 

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

978 for element in self.value), 

979 self.typ) 

980 

981 def resolve_references(self, mh): 

982 assert isinstance(mh, Message_Handler) 

983 

984 for val in self.value: 

985 val.resolve_references(mh) 

986 

987 def to_python_object(self): 

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

989 

990 def can_be_null(self): 

991 return False 

992 

993 def uses_field_access(self): 

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

995 

996 

997class Tuple_Aggregate(Expression): 

998 """Instances of a tuple 

999 

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

1001 two forms, the ordinary form:: 

1002 

1003 coordinate = (12.3, 40.0) 

1004 ^^^^^^^^^^^^ 

1005 

1006 And the separator form:: 

1007 

1008 item = 12345@42 

1009 ^^^^^^^^ 

1010 

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

1012 syntactic sugar. 

1013 

1014 :attribute value: contents of the tuple 

1015 :type: dict[str, Expression] 

1016 

1017 """ 

1018 def __init__(self, location, typ): 

1019 # lobster-trace: LRM.Unspecified_Optional_Components 

1020 # lobster-trace: LRM.Record_Object_Declaration 

1021 

1022 super().__init__(location, typ) 

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

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

1025 

1026 def assign(self, field, value): 

1027 assert isinstance(field, str) 

1028 assert isinstance(value, (Literal, 

1029 Unary_Expression, 

1030 Tuple_Aggregate, 

1031 Record_Reference)), \ 

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

1033 assert field in self.typ.components 

1034 

1035 self.value[field] = value 

1036 

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

1038 # lobster-exclude: Debugging feature 

1039 self.write_indent(indent, "Tuple_Aggregate") 

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

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

1042 if isinstance(n_item, Composite_Component): 

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

1044 

1045 def to_string(self): 

1046 first = True 

1047 if self.typ.has_separators(): 

1048 rv = "" 

1049 else: 

1050 rv = "(" 

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

1052 if isinstance(n_item, Separator): 

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

1054 elif first: 

1055 first = False 

1056 else: 

1057 rv += ", " 

1058 

1059 if isinstance(n_item, Composite_Component): 

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

1061 if self.typ.has_separators(): 

1062 rv = "" 

1063 else: 

1064 rv = ")" 

1065 return rv 

1066 

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

1068 assert isinstance(mh, Message_Handler) 

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

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

1071 return Value(self.location, 

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

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

1074 self.typ) 

1075 

1076 def resolve_references(self, mh): 

1077 assert isinstance(mh, Message_Handler) 

1078 

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

1080 val.resolve_references(mh) 

1081 

1082 def to_python_object(self): 

1083 return {name: value.to_python_object() 

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

1085 

1086 def can_be_null(self): 

1087 return False 

1088 

1089 def uses_field_access(self): 

1090 return any(expr.uses_field_access() 

1091 for expr in self.value.values()) 

1092 

1093 

1094class Record_Reference(Expression): 

1095 """Reference to another record object 

1096 

1097 This can appear in record object declarations:: 

1098 

1099 Requirement Kitten { 

1100 depends_on = Other_Package.Cat 

1101 ^1 ^2 

1102 } 

1103 

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

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

1106 the target attribute. 

1107 

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

1109 immediately resolved on parsing in the TRLC language. 

1110 

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

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

1113 in this AST node. 

1114 

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

1116 :type: str 

1117 

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

1119 :type: Record_Object 

1120 

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

1122 :type: Package 

1123 

1124 """ 

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

1126 # lobster-exclude: Constructor only declares variables 

1127 assert isinstance(location, Location) 

1128 assert isinstance(name, str) 

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

1130 assert isinstance(package, Package) 

1131 super().__init__(location, typ) 

1132 

1133 self.name = name 

1134 self.target = None 

1135 self.package = package 

1136 

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

1138 # lobster-exclude: Debugging feature 

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

1140 self.write_indent(indent + 1, 

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

1142 

1143 def to_string(self): 

1144 return self.name 

1145 

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

1147 assert isinstance(mh, Message_Handler) 

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

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

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

1151 

1152 def resolve_references(self, mh): 

1153 # lobster-trace: LRM.References_To_Extensions 

1154 # lobster-trace: LRM.Union_Type_Minimum_Members 

1155 assert isinstance(mh, Message_Handler) 

1156 

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

1158 mh = mh, 

1159 name = self.name, 

1160 error_location = self.location, 

1161 required_subclass = Record_Object) 

1162 if self.typ is None: 

1163 self.typ = self.target.n_typ 

1164 elif isinstance(self.typ, Union_Type): 

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

1166 mh.error(self.location, 

1167 "expected reference of type %s," 

1168 " but %s is of type %s" % 

1169 (self.typ.name, 

1170 self.target.name, 

1171 self.target.n_typ.name)) 

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

1173 mh.error(self.location, 

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

1175 (self.typ.name, 

1176 self.target.name, 

1177 self.target.n_typ.name)) 

1178 

1179 def to_python_object(self): 

1180 return self.target.fully_qualified_name() 

1181 

1182 def can_be_null(self): 

1183 return False 

1184 

1185 

1186class Name_Reference(Expression): 

1187 # lobster-trace: LRM.Qualified_Name 

1188 # lobster-trace: LRM.Static_Regular_Expression 

1189 

1190 """Reference to a name 

1191 

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

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

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

1195 

1196 For example:: 

1197 

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

1199 ^1 ^2 

1200 

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

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

1203 :class:`Quantified_Variable`. 

1204 

1205 :attribute entity: the entity named here 

1206 :type: Composite_Component, Quantified_Variable 

1207 """ 

1208 def __init__(self, location, entity): 

1209 assert isinstance(entity, (Composite_Component, 

1210 Quantified_Variable)) 

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

1212 self.entity = entity 

1213 

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

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

1216 

1217 def to_string(self): 

1218 return self.entity.name 

1219 

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

1221 assert isinstance(mh, Message_Handler) 

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

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

1224 

1225 if context is None: 

1226 mh.error(self.location, 

1227 "cannot be used in a static context") 

1228 

1229 assert self.entity.name in context 

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

1231 

1232 def can_be_null(self): 

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

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

1235 # optional. 

1236 if isinstance(self.entity, Composite_Component): 

1237 return self.entity.optional 

1238 else: 

1239 return False 

1240 

1241 

1242class Unary_Expression(Expression): 

1243 """Expression with only one operand 

1244 

1245 This captures the following operations: 

1246 

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

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

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

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

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

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

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

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

1255 

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

1257 

1258 :attribute operator: the operation 

1259 :type: Unary_Operator 

1260 

1261 :attribute n_operand: the expression we operate on 

1262 :type: Expression 

1263 

1264 """ 

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

1266 # lobster-trace: LRM.Simple_Expression 

1267 # lobster-trace: LRM.Relation 

1268 # lobster-trace: LRM.Factor 

1269 # lobster-trace: LRM.Signature_Len 

1270 # lobster-trace: LRM.Signature_Type_Conversion 

1271 

1272 super().__init__(location, typ) 

1273 assert isinstance(mh, Message_Handler) 

1274 assert isinstance(operator, Unary_Operator) 

1275 assert isinstance(n_operand, Expression) 

1276 self.operator = operator 

1277 self.n_operand = n_operand 

1278 

1279 if operator in (Unary_Operator.MINUS, 

1280 Unary_Operator.PLUS, 

1281 Unary_Operator.ABSOLUTE_VALUE): 

1282 self.n_operand.ensure_type(mh, Builtin_Numeric_Type) 

1283 elif operator == Unary_Operator.LOGICAL_NOT: 

1284 self.n_operand.ensure_type(mh, Builtin_Boolean) 

1285 elif operator == Unary_Operator.STRING_LENGTH: 

1286 self.n_operand.ensure_type(mh, Builtin_String) 

1287 elif operator == Unary_Operator.ARRAY_LENGTH: 

1288 self.n_operand.ensure_type(mh, Array_Type) 

1289 elif operator == Unary_Operator.CONVERSION_TO_INT: 

1290 self.n_operand.ensure_type(mh, Builtin_Numeric_Type) 

1291 elif operator == Unary_Operator.CONVERSION_TO_DECIMAL: 

1292 self.n_operand.ensure_type(mh, Builtin_Numeric_Type) 

1293 else: 

1294 mh.ice_loc(self.location, 

1295 "unexpected unary operation %s" % operator) 

1296 

1297 def to_string(self): 

1298 prefix_operators = { 

1299 Unary_Operator.MINUS : "-", 

1300 Unary_Operator.PLUS : "+", 

1301 Unary_Operator.ABSOLUTE_VALUE : "abs ", 

1302 Unary_Operator.LOGICAL_NOT : "not ", 

1303 } 

1304 function_calls = { 

1305 Unary_Operator.STRING_LENGTH : "len", 

1306 Unary_Operator.ARRAY_LENGTH : "len", 

1307 Unary_Operator.CONVERSION_TO_INT : "Integer", 

1308 Unary_Operator.CONVERSION_TO_DECIMAL : "Decimal" 

1309 } 

1310 

1311 if self.operator in prefix_operators: 

1312 return prefix_operators[self.operator] + \ 

1313 self.n_operand.to_string() 

1314 

1315 elif self.operator in function_calls: 

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

1317 self.n_operand.to_string()) 

1318 

1319 else: 

1320 assert False 

1321 

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

1323 # lobster-exclude: Debugging feature 

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

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

1326 self.n_operand.dump(indent + 1) 

1327 

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

1329 # lobster-trace: LRM.Null_Is_Invalid 

1330 # lobster-trace: LRM.Signature_Len 

1331 # lobster-trace: LRM.Signature_Type_Conversion 

1332 # lobster-trace: LRM.Len_Semantics 

1333 # lobster-trace: LRM.Integer_Conversion_Semantics 

1334 # lobster-trace: LRM.Decimal_Conversion_Semantics 

1335 

1336 assert isinstance(mh, Message_Handler) 

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

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

1339 

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

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

1342 mh.error(v_operand.location, 

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

1344 (self.to_string(), 

1345 mh.cross_file_reference(self.location))) 

1346 

1347 if self.operator == Unary_Operator.MINUS: 

1348 return Value(location = self.location, 

1349 value = -v_operand.value, 

1350 typ = self.typ) 

1351 elif self.operator == Unary_Operator.PLUS: 

1352 return Value(location = self.location, 

1353 value = +v_operand.value, 

1354 typ = self.typ) 

1355 elif self.operator == Unary_Operator.LOGICAL_NOT: 

1356 return Value(location = self.location, 

1357 value = not v_operand.value, 

1358 typ = self.typ) 

1359 elif self.operator == Unary_Operator.ABSOLUTE_VALUE: 

1360 return Value(location = self.location, 

1361 value = abs(v_operand.value), 

1362 typ = self.typ) 

1363 elif self.operator in (Unary_Operator.STRING_LENGTH, 

1364 Unary_Operator.ARRAY_LENGTH): 

1365 return Value(location = self.location, 

1366 value = len(v_operand.value), 

1367 typ = self.typ) 

1368 elif self.operator == Unary_Operator.CONVERSION_TO_INT: 

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

1370 return Value( 

1371 location = self.location, 

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

1373 typ = self.typ) 

1374 else: 

1375 return Value(location = self.location, 

1376 value = v_operand.value, 

1377 typ = self.typ) 

1378 elif self.operator == Unary_Operator.CONVERSION_TO_DECIMAL: 

1379 return Value(location = self.location, 

1380 value = Fraction(v_operand.value), 

1381 typ = self.typ) 

1382 else: 

1383 mh.ice_loc(self.location, 

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

1385 

1386 def to_python_object(self): 

1387 assert self.operator in (Unary_Operator.MINUS, 

1388 Unary_Operator.PLUS) 

1389 val = self.n_operand.to_python_object() 

1390 if self.operator == Unary_Operator.MINUS: 

1391 return -val 

1392 else: 

1393 return val 

1394 

1395 def can_be_null(self): 

1396 return False 

1397 

1398 def uses_field_access(self): 

1399 return self.n_operand.uses_field_access() 

1400 

1401 

1402class Binary_Expression(Expression): 

1403 """Expression with two operands 

1404 

1405 This captures the following operations: 

1406 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1429 

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

1431 

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

1433 rationals and strings. 

1434 

1435 :attribute operator: the operation 

1436 :type: Binary_Operator 

1437 

1438 :attribute n_lhs: the first operand 

1439 :type: Expression 

1440 

1441 :attribute n_rhs: the second operand 

1442 :type: Expression 

1443 

1444 """ 

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

1446 # lobster-trace: LRM.Expression 

1447 # lobster-trace: LRM.Relation 

1448 # lobster-trace: LRM.Simple_Expression 

1449 # lobster-trace: LRM.Term 

1450 # lobster-trace: LRM.Factor 

1451 # lobster-trace: LRM.Signature_String_End_Functions 

1452 # lobster-trace: LRM.Signature_Matches 

1453 

1454 super().__init__(location, typ) 

1455 assert isinstance(mh, Message_Handler) 

1456 assert isinstance(operator, Binary_Operator) 

1457 assert isinstance(n_lhs, Expression) 

1458 assert isinstance(n_rhs, Expression) 

1459 self.operator = operator 

1460 self.n_lhs = n_lhs 

1461 self.n_rhs = n_rhs 

1462 

1463 if operator in (Binary_Operator.LOGICAL_AND, 

1464 Binary_Operator.LOGICAL_OR, 

1465 Binary_Operator.LOGICAL_XOR, 

1466 Binary_Operator.LOGICAL_IMPLIES): 

1467 self.n_lhs.ensure_type(mh, Builtin_Boolean) 

1468 self.n_rhs.ensure_type(mh, Builtin_Boolean) 

1469 

1470 elif operator in (Binary_Operator.COMP_EQ, 

1471 Binary_Operator.COMP_NEQ): 

1472 # lobster-trace: LRM.Union_Type_Equality 

1473 # lobster-trace: LRM.Union_Type_Equality_Domain 

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

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

1476 pass 

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

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

1479 # For union types, we allow comparison if both 

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

1481 lhs_is_record = isinstance(self.n_lhs.typ, 

1482 (Record_Type, Union_Type)) 

1483 rhs_is_record = isinstance(self.n_rhs.typ, 

1484 (Record_Type, Union_Type)) 

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

1486 mh.error(self.location, 

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

1488 (self.n_lhs.typ.name, 

1489 self.n_rhs.typ.name)) 

1490 else: 

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

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

1493 # of the other. This implements Equality_Domain for 

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

1495 lhs_members = (self.n_lhs.typ.types 

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

1497 else [self.n_lhs.typ]) 

1498 rhs_members = (self.n_rhs.typ.types 

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

1500 else [self.n_rhs.typ]) 

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

1502 for lm in lhs_members 

1503 for rm in rhs_members): 

1504 mh.error(self.location, 

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

1506 (self.n_lhs.typ.name, 

1507 self.n_rhs.typ.name)) 

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

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

1510 # types match 

1511 mh.error(self.location, 

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

1513 (self.n_lhs.typ.name, 

1514 self.n_rhs.typ.name)) 

1515 

1516 elif operator in (Binary_Operator.COMP_LT, 

1517 Binary_Operator.COMP_LEQ, 

1518 Binary_Operator.COMP_GT, 

1519 Binary_Operator.COMP_GEQ): 

1520 self.n_lhs.ensure_type(mh, Builtin_Numeric_Type) 

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

1522 

1523 elif operator in (Binary_Operator.STRING_CONTAINS, 

1524 Binary_Operator.STRING_STARTSWITH, 

1525 Binary_Operator.STRING_ENDSWITH, 

1526 Binary_Operator.STRING_REGEX): 

1527 self.n_lhs.ensure_type(mh, Builtin_String) 

1528 self.n_rhs.ensure_type(mh, Builtin_String) 

1529 

1530 elif operator == Binary_Operator.ARRAY_CONTAINS: 

1531 self.n_rhs.ensure_type(mh, Array_Type) 

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

1533 

1534 elif operator == Binary_Operator.PLUS: 

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

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

1537 else: 

1538 self.n_lhs.ensure_type(mh, Builtin_String) 

1539 self.n_rhs.ensure_type(mh, Builtin_String) 

1540 

1541 elif operator in (Binary_Operator.MINUS, 

1542 Binary_Operator.TIMES, 

1543 Binary_Operator.DIVIDE): 

1544 self.n_lhs.ensure_type(mh, Builtin_Numeric_Type) 

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

1546 

1547 elif operator == Binary_Operator.POWER: 

1548 self.n_lhs.ensure_type(mh, Builtin_Numeric_Type) 

1549 self.n_rhs.ensure_type(mh, Builtin_Integer) 

1550 

1551 elif operator == Binary_Operator.REMAINDER: 

1552 self.n_lhs.ensure_type(mh, Builtin_Integer) 

1553 self.n_rhs.ensure_type(mh, Builtin_Integer) 

1554 

1555 elif operator == Binary_Operator.INDEX: 

1556 self.n_lhs.ensure_type(mh, Array_Type) 

1557 self.n_rhs.ensure_type(mh, Builtin_Integer) 

1558 

1559 else: 

1560 mh.ice_loc(self.location, 

1561 "unexpected binary operation %s" % operator) 

1562 

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

1564 # lobster-exclude: Debugging feature 

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

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

1567 self.n_lhs.dump(indent + 1) 

1568 self.n_rhs.dump(indent + 1) 

1569 

1570 def to_string(self): 

1571 infix_operators = { 

1572 Binary_Operator.LOGICAL_AND : "and", 

1573 Binary_Operator.LOGICAL_OR : "or", 

1574 Binary_Operator.LOGICAL_XOR : "xor", 

1575 Binary_Operator.LOGICAL_IMPLIES : "implies", 

1576 Binary_Operator.COMP_EQ : "==", 

1577 Binary_Operator.COMP_NEQ : "!=", 

1578 Binary_Operator.COMP_LT : "<", 

1579 Binary_Operator.COMP_LEQ : "<=", 

1580 Binary_Operator.COMP_GT : ">", 

1581 Binary_Operator.COMP_GEQ : ">=", 

1582 Binary_Operator.STRING_CONTAINS : "in", 

1583 Binary_Operator.ARRAY_CONTAINS : "in", 

1584 Binary_Operator.PLUS : "+", 

1585 Binary_Operator.MINUS : "-", 

1586 Binary_Operator.TIMES : "*", 

1587 Binary_Operator.DIVIDE : "/", 

1588 Binary_Operator.REMAINDER : "%", 

1589 Binary_Operator.POWER : "**", 

1590 } 

1591 string_functions = { 

1592 Binary_Operator.STRING_STARTSWITH : "startswith", 

1593 Binary_Operator.STRING_ENDSWITH : "endswith", 

1594 Binary_Operator.STRING_REGEX : "matches", 

1595 } 

1596 

1597 if self.operator in infix_operators: 

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

1599 infix_operators[self.operator], 

1600 self.n_rhs.to_string()) 

1601 

1602 elif self.operator in string_functions: 

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

1604 self.n_lhs.to_string(), 

1605 self.n_rhs.to_string()) 

1606 

1607 elif self.operator == Binary_Operator.INDEX: 

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

1609 self.n_rhs.to_string()) 

1610 

1611 else: 

1612 assert False 

1613 

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

1615 # lobster-trace: LRM.Null_Equivalence 

1616 # lobster-trace: LRM.Null_Is_Invalid 

1617 # lobster-trace: LRM.Signature_String_End_Functions 

1618 # lobster-trace: LRM.Signature_Matches 

1619 # lobster-trace: LRM.Startswith_Semantics 

1620 # lobster-trace: LRM.Endswith_Semantics 

1621 # lobster-trace: LRM.Matches_Semantics 

1622 

1623 assert isinstance(mh, Message_Handler) 

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

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

1626 

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

1628 if v_lhs.value is None and \ 

1629 self.operator not in (Binary_Operator.COMP_EQ, 

1630 Binary_Operator.COMP_NEQ): 

1631 mh.error(v_lhs.location, 

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

1633 (self.to_string(), 

1634 mh.cross_file_reference(self.location))) 

1635 

1636 # Check for the short-circuit operators first 

1637 if self.operator == Binary_Operator.LOGICAL_AND: 

1638 assert isinstance(v_lhs.value, bool) 

1639 if v_lhs.value: 

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

1641 else: 

1642 return v_lhs 

1643 

1644 elif self.operator == Binary_Operator.LOGICAL_OR: 

1645 assert isinstance(v_lhs.value, bool) 

1646 if v_lhs.value: 

1647 return v_lhs 

1648 else: 

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

1650 

1651 elif self.operator == Binary_Operator.LOGICAL_IMPLIES: 

1652 assert isinstance(v_lhs.value, bool) 

1653 if v_lhs.value: 

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

1655 else: 

1656 return Value(location = self.location, 

1657 value = True, 

1658 typ = self.typ) 

1659 

1660 # Otherwise, evaluate RHS and do the operation 

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

1662 if v_rhs.value is None and \ 

1663 self.operator not in (Binary_Operator.COMP_EQ, 

1664 Binary_Operator.COMP_NEQ): 

1665 mh.error(v_rhs.location, 

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

1667 (self.to_string(), 

1668 mh.cross_file_reference(self.location))) 

1669 

1670 if self.operator == Binary_Operator.LOGICAL_XOR: 

1671 assert isinstance(v_lhs.value, bool) 

1672 assert isinstance(v_rhs.value, bool) 

1673 return Value(location = self.location, 

1674 value = v_lhs.value ^ v_rhs.value, 

1675 typ = self.typ) 

1676 

1677 elif self.operator == Binary_Operator.COMP_EQ: 

1678 return Value(location = self.location, 

1679 value = v_lhs.value == v_rhs.value, 

1680 typ = self.typ) 

1681 

1682 elif self.operator == Binary_Operator.COMP_NEQ: 

1683 return Value(location = self.location, 

1684 value = v_lhs.value != v_rhs.value, 

1685 typ = self.typ) 

1686 

1687 elif self.operator in (Binary_Operator.COMP_LT, 

1688 Binary_Operator.COMP_LEQ, 

1689 Binary_Operator.COMP_GT, 

1690 Binary_Operator.COMP_GEQ): 

1691 return Value( 

1692 location = self.location, 

1693 value = { 

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

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

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

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

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

1699 typ = self.typ) 

1700 

1701 elif self.operator == Binary_Operator.STRING_CONTAINS: 

1702 assert isinstance(v_lhs.value, str) 

1703 assert isinstance(v_rhs.value, str) 

1704 

1705 return Value(location = self.location, 

1706 value = v_lhs.value in v_rhs.value, 

1707 typ = self.typ) 

1708 

1709 elif self.operator == Binary_Operator.STRING_STARTSWITH: 

1710 assert isinstance(v_lhs.value, str) 

1711 assert isinstance(v_rhs.value, str) 

1712 return Value(location = self.location, 

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

1714 typ = self.typ) 

1715 

1716 elif self.operator == Binary_Operator.STRING_ENDSWITH: 

1717 assert isinstance(v_lhs.value, str) 

1718 assert isinstance(v_rhs.value, str) 

1719 return Value(location = self.location, 

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

1721 typ = self.typ) 

1722 

1723 elif self.operator == Binary_Operator.STRING_REGEX: 

1724 assert isinstance(v_lhs.value, str) 

1725 assert isinstance(v_rhs.value, str) 

1726 return Value(location = self.location, 

1727 value = re.match(v_rhs.value, 

1728 v_lhs.value) is not None, 

1729 typ = self.typ) 

1730 

1731 elif self.operator == Binary_Operator.ARRAY_CONTAINS: 

1732 assert isinstance(v_rhs.value, list) 

1733 

1734 return Value(location = self.location, 

1735 value = v_lhs in v_rhs.value, 

1736 typ = self.typ) 

1737 

1738 elif self.operator == Binary_Operator.PLUS: 

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

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

1741 return Value(location = self.location, 

1742 value = v_lhs.value + v_rhs.value, 

1743 typ = self.typ) 

1744 

1745 elif self.operator == Binary_Operator.MINUS: 

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

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

1748 return Value(location = self.location, 

1749 value = v_lhs.value - v_rhs.value, 

1750 typ = self.typ) 

1751 

1752 elif self.operator == Binary_Operator.TIMES: 

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

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

1755 return Value(location = self.location, 

1756 value = v_lhs.value * v_rhs.value, 

1757 typ = self.typ) 

1758 

1759 elif self.operator == Binary_Operator.DIVIDE: 

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

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

1762 

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

1764 mh.error(v_rhs.location, 

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

1766 (self.to_string(), 

1767 mh.cross_file_reference(self.location))) 

1768 

1769 if isinstance(v_lhs.value, int): 

1770 return Value(location = self.location, 

1771 value = v_lhs.value // v_rhs.value, 

1772 typ = self.typ) 

1773 else: 

1774 return Value(location = self.location, 

1775 value = v_lhs.value / v_rhs.value, 

1776 typ = self.typ) 

1777 

1778 elif self.operator == Binary_Operator.REMAINDER: 

1779 assert isinstance(v_lhs.value, int) 

1780 assert isinstance(v_rhs.value, int) 

1781 

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

1783 mh.error(v_rhs.location, 

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

1785 (self.to_string(), 

1786 mh.cross_file_reference(self.location))) 

1787 

1788 return Value(location = self.location, 

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

1790 typ = self.typ) 

1791 

1792 elif self.operator == Binary_Operator.POWER: 

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

1794 assert isinstance(v_rhs.value, int) 

1795 return Value(location = self.location, 

1796 value = v_lhs.value ** v_rhs.value, 

1797 typ = self.typ) 

1798 

1799 elif self.operator == Binary_Operator.INDEX: 

1800 assert isinstance(v_lhs.value, list) 

1801 assert isinstance(v_rhs.value, int) 

1802 

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

1804 mh.error(v_rhs.location, 

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

1806 (self.to_string(), 

1807 mh.cross_file_reference(self.location))) 

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

1809 v_rhs.value > v_lhs.typ.upper_bound: 

1810 mh.error(v_rhs.location, 

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

1812 (v_lhs.typ.upper_bound, 

1813 self.to_string(), 

1814 mh.cross_file_reference(self.location))) 

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

1816 mh.error(v_lhs.location, 

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

1818 (self.to_string(), 

1819 mh.cross_file_reference(self.location))) 

1820 

1821 return Value(location = self.location, 

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

1823 typ = self.typ) 

1824 

1825 else: 

1826 mh.ice_loc(self.location, 

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

1828 

1829 def can_be_null(self): 

1830 return False 

1831 

1832 def uses_field_access(self): 

1833 return (self.n_lhs.uses_field_access() or 

1834 self.n_rhs.uses_field_access()) 

1835 

1836 

1837class Field_Access_Expression(Expression): 

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

1839 

1840 For example in:: 

1841 

1842 foo.bar 

1843 ^1 ^2 

1844 

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

1846 :type: Expression 

1847 

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

1849 :type: Composite_Component 

1850 

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

1852 :type: bool 

1853 

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

1855 Only meaningful when is_union_access is True. 

1856 :type: bool 

1857 

1858 """ 

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

1860 is_union_access=False, is_universal=True): 

1861 # lobster-trace: LRM.Union_Type_Field_Access 

1862 assert isinstance(mh, Message_Handler) 

1863 assert isinstance(n_prefix, Expression) 

1864 assert isinstance(n_field, Composite_Component) 

1865 assert isinstance(is_union_access, bool) 

1866 assert isinstance(is_universal, bool) 

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

1868 self.n_prefix = n_prefix 

1869 self.n_field = n_field 

1870 self.is_union_access = is_union_access 

1871 self.is_universal = is_universal 

1872 

1873 if not is_union_access: 

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

1875 

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

1877 # lobster-exclude: Debugging feature 

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

1879 self.n_prefix.dump(indent + 1) 

1880 

1881 def to_string(self): 

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

1883 

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

1885 assert isinstance(mh, Message_Handler) 

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

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

1888 

1889 v_prefix = self.n_prefix.evaluate(mh, 

1890 context, 

1891 gstab).value 

1892 if v_prefix is None: 

1893 # lobster-trace: LRM.Dereference 

1894 mh.error(self.n_prefix.location, 

1895 "null dereference") 

1896 

1897 # lobster-trace: LRM.Union_Type_Partial_Field_Access 

1898 # lobster-trace: LRM.Union_Type_Partial_Field_Null 

1899 if self.n_field.name not in v_prefix: 

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

1901 

1902 v_field = v_prefix[self.n_field.name] 

1903 if isinstance(v_field, Expression): 

1904 # lobster-trace: LRM.Dereference 

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

1906 else: 

1907 return v_field 

1908 

1909 def can_be_null(self): 

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

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

1912 # report True in that case. 

1913 return self.is_union_access and not self.is_universal 

1914 

1915 def uses_field_access(self): 

1916 # lobster-trace: LRM.Dereference 

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

1918 return True 

1919 return self.n_prefix.uses_field_access() 

1920 

1921 

1922class Range_Test(Expression): 

1923 """Range membership test 

1924 

1925 For example in:: 

1926 

1927 x in 1 .. field+1 

1928 ^lhs ^lower ^^^^^^^upper 

1929 

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

1931 you can have arbitrarily complex expressions here. 

1932 

1933 :attribute n_lhs: the expression to test 

1934 :type: Expression 

1935 

1936 :attribute n_lower: the lower bound 

1937 :type: Expression 

1938 

1939 :attribute n_upper: the upper bound 

1940 :type: Expression 

1941 

1942 """ 

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

1944 # lobster-trace: LRM.Relation 

1945 super().__init__(location, typ) 

1946 assert isinstance(mh, Message_Handler) 

1947 assert isinstance(n_lhs, Expression) 

1948 assert isinstance(n_lower, Expression) 

1949 assert isinstance(n_upper, Expression) 

1950 self.n_lhs = n_lhs 

1951 self.n_lower = n_lower 

1952 self.n_upper = n_upper 

1953 

1954 self.n_lhs.ensure_type(mh, Builtin_Numeric_Type) 

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

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

1957 

1958 def to_string(self): 

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

1960 self.n_lower.to_string(), 

1961 self.n_upper.to_string()) 

1962 

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

1964 # lobster-exclude: Debugging feature 

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

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

1967 self.n_lhs.dump(indent + 1) 

1968 self.n_lower.dump(indent + 1) 

1969 self.n_upper.dump(indent + 1) 

1970 

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

1972 # lobster-trace: LRM.Null_Is_Invalid 

1973 assert isinstance(mh, Message_Handler) 

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

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

1976 

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

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

1979 mh.error(v_lhs.location, 

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

1981 (self.to_string(), 

1982 mh.cross_file_reference(self.location))) 

1983 

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

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

1986 mh.error(v_lower.location, 

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

1988 (self.to_string(), 

1989 mh.cross_file_reference(self.location))) 

1990 

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

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

1993 mh.error(v_upper.location, 

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

1995 (self.to_string(), 

1996 mh.cross_file_reference(self.location))) 

1997 

1998 return Value(location = self.location, 

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

2000 typ = self.typ) 

2001 

2002 def can_be_null(self): 

2003 return False 

2004 

2005 def uses_field_access(self): 

2006 return (self.n_lhs.uses_field_access() or 

2007 self.n_lower.uses_field_access() or 

2008 self.n_upper.uses_field_access()) 

2009 

2010 

2011class OneOf_Expression(Expression): 

2012 """OneOf expression 

2013 

2014 For example in:: 

2015 

2016 oneof(a, b, c) 

2017 ^^^^^^^ choices 

2018 

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

2020 :type: list[Expression] 

2021 """ 

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

2023 # lobster-trace: LRM.Signature_OneOf 

2024 super().__init__(location, typ) 

2025 assert isinstance(typ, Builtin_Boolean) 

2026 assert isinstance(mh, Message_Handler) 

2027 assert isinstance(choices, list) 

2028 assert all(isinstance(item, Expression) 

2029 for item in choices) 

2030 self.choices = choices 

2031 

2032 for n_choice in choices: 

2033 n_choice.ensure_type(mh, Builtin_Boolean) 

2034 

2035 def to_string(self): 

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

2037 for n_choice in self.choices) 

2038 

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

2040 # lobster-exclude: Debugging feature 

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

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

2043 for n_choice in self.choices: 

2044 n_choice.dump(indent + 1) 

2045 

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

2047 # lobster-trace: LRM.OneOf_Semantics 

2048 assert isinstance(mh, Message_Handler) 

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

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

2051 

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

2053 for n_choice in self.choices] 

2054 

2055 return Value(location = self.location, 

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

2057 typ = self.typ) 

2058 

2059 def can_be_null(self): 

2060 return False 

2061 

2062 def uses_field_access(self): 

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

2064 

2065 

2066class Action(Node): 

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

2068 

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

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

2071 Actions:: 

2072 

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

2074 ^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^ 

2075 

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

2077 the :class:`Conditional_Expression` itself. 

2078 

2079 :attribute kind: Either if or elseif 

2080 :type: str 

2081 

2082 :attribute n_cond: The boolean condition expression 

2083 :type: Expression 

2084 

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

2086 :type: Expression 

2087 

2088 """ 

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

2090 # lobster-trace: LRM.Conditional_Expression 

2091 assert isinstance(mh, Message_Handler) 

2092 assert isinstance(t_kind, Token) 

2093 assert t_kind.kind == "KEYWORD" 

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

2095 assert isinstance(n_condition, Expression) 

2096 assert isinstance(n_expression, Expression) 

2097 super().__init__(t_kind.location) 

2098 self.kind = t_kind.value 

2099 self.n_cond = n_condition 

2100 self.n_expr = n_expression 

2101 # lobster-trace: LRM.Conditional_Expression_Types 

2102 self.n_cond.ensure_type(mh, Builtin_Boolean) 

2103 

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

2105 # lobster-exclude: Debugging feature 

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

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

2108 self.n_cond.dump(indent + 2) 

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

2110 self.n_expr.dump(indent + 2) 

2111 

2112 def to_string(self): 

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

2114 self.n_cond.to_string(), 

2115 self.n_expr.to_string()) 

2116 

2117 

2118class Conditional_Expression(Expression): 

2119 """A conditional expression 

2120 

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

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

2123 expression with two Actions:: 

2124 

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

2126 ^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^ 

2127 

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

2129 

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

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

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

2133 of all actions and the else expression will match. 

2134 

2135 :attribute actions: a list of Actions 

2136 :type: list[Action] 

2137 

2138 :attribute else_expr: the else expression 

2139 :type: Expression 

2140 

2141 """ 

2142 def __init__(self, location, if_action): 

2143 # lobster-trace: LRM.Conditional_Expression 

2144 assert isinstance(if_action, Action) 

2145 assert if_action.kind == "if" 

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

2147 self.actions = [if_action] 

2148 self.else_expr = None 

2149 

2150 def add_elsif(self, mh, n_action): 

2151 # lobster-trace: LRM.Conditional_Expression 

2152 # lobster-trace; LRM.Conditional_Expression_Types 

2153 assert isinstance(mh, Message_Handler) 

2154 assert isinstance(n_action, Action) 

2155 assert n_action.kind == "elsif" 

2156 

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

2158 self.actions.append(n_action) 

2159 

2160 def set_else_part(self, mh, n_expr): 

2161 # lobster-trace: LRM.Conditional_Expression 

2162 # lobster-trace; LRM.Conditional_Expression_Types 

2163 assert isinstance(mh, Message_Handler) 

2164 assert isinstance(n_expr, Expression) 

2165 

2166 n_expr.ensure_type(mh, self.typ) 

2167 self.else_expr = n_expr 

2168 

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

2170 # lobster-exclude: Debugging feature 

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

2172 for action in self.actions: 

2173 action.dump(indent + 1) 

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

2175 self.else_expr.dump(indent + 2) 

2176 

2177 def to_string(self): 

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

2179 for action in self.actions) 

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

2181 rv += ")" 

2182 return rv 

2183 

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

2185 # lobster-trace: LRM.Conditional_Expression_Else 

2186 # lobster-trace: LRM.Conditional_Expression_Evaluation 

2187 # lobster-trace: LRM.Null_Is_Invalid 

2188 assert isinstance(mh, Message_Handler) 

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

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

2191 

2192 for action in self.actions: 

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

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

2195 mh.error(v_cond.location, 

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

2197 (action.to_string(), 

2198 mh.cross_file_reference(self.location))) 

2199 if v_cond.value: 

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

2201 

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

2203 

2204 def can_be_null(self): 

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

2206 return True 

2207 

2208 return any(action.n_expr.can_be_null() 

2209 for action in self.actions) 

2210 

2211 def uses_field_access(self): 

2212 return (any(action.n_cond.uses_field_access() or 

2213 action.n_expr.uses_field_access() 

2214 for action in self.actions) or 

2215 (self.else_expr is not None and 

2216 self.else_expr.uses_field_access())) 

2217 

2218 

2219class Quantified_Expression(Expression): 

2220 """A quantified expression 

2221 

2222 For example:: 

2223 

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

2225 ^4 ^1 ^2 ^^^^^3 

2226 

2227 A quantified expression introduces and binds a 

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

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

2230 each component of the source in turn. 

2231 

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

2233 :type: Quantified_Variable 

2234 

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

2236 :type: Name_Reference 

2237 

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

2239 :type: Expression 

2240 

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

2242 :type: Boolean 

2243 

2244 """ 

2245 def __init__(self, mh, location, 

2246 typ, 

2247 universal, 

2248 n_variable, 

2249 n_source, 

2250 n_expr): 

2251 # lobster-trace: LRM.Quantified_Expression 

2252 # lobster-trace: LRM.Quantification_Type 

2253 super().__init__(location, typ) 

2254 assert isinstance(typ, Builtin_Boolean) 

2255 assert isinstance(universal, bool) 

2256 assert isinstance(n_variable, Quantified_Variable) 

2257 assert isinstance(n_expr, Expression) 

2258 assert isinstance(n_source, Name_Reference) 

2259 self.universal = universal 

2260 self.n_var = n_variable 

2261 self.n_expr = n_expr 

2262 self.n_source = n_source 

2263 self.n_expr.ensure_type(mh, Builtin_Boolean) 

2264 

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

2266 # lobster-exclude: Debugging feature 

2267 if self.universal: 

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

2269 else: 

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

2271 self.n_var.dump(indent + 1) 

2272 self.n_expr.dump(indent + 1) 

2273 

2274 def to_string(self): 

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

2276 if self.universal 

2277 else "exists", 

2278 self.n_var.name, 

2279 self.n_source.to_string(), 

2280 self.n_expr.to_string()) 

2281 

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

2283 # lobster-trace: LRM.Null_Is_Invalid 

2284 # lobster-trace: LRM.Universal_Quantification_Semantics 

2285 # lobster-trace: LRM.Existential_Quantification_Semantics 

2286 assert isinstance(mh, Message_Handler) 

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

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

2289 

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

2291 new_ctx = {} 

2292 else: 

2293 new_ctx = copy(context) 

2294 

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

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

2297 # error messages. 

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

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

2300 if isinstance(array_values, Implicit_Null): 

2301 mh.error(array_values.location, 

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

2303 "must not be null" % 

2304 (self.n_source.to_string(), 

2305 self.to_string(), 

2306 mh.cross_file_reference(self.location))) 

2307 else: 

2308 assert isinstance(array_values, Array_Aggregate) 

2309 

2310 rv = self.universal 

2311 loc = self.location 

2312 for binding in array_values.value: 

2313 new_ctx[self.n_var.name] = binding 

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

2315 assert isinstance(result.value, bool) 

2316 if self.universal and not result.value: 

2317 rv = False 

2318 loc = binding.location 

2319 break 

2320 elif not self.universal and result.value: 

2321 rv = True 

2322 loc = binding.location 

2323 break 

2324 

2325 return Value(location = loc, 

2326 value = rv, 

2327 typ = self.typ) 

2328 

2329 def can_be_null(self): 

2330 return False 

2331 

2332 def uses_field_access(self): 

2333 return (self.n_source.uses_field_access() or 

2334 self.n_expr.uses_field_access()) 

2335 

2336 

2337############################################################################## 

2338# AST Nodes (Entities) 

2339############################################################################## 

2340 

2341class Entity(Node, metaclass=ABCMeta): 

2342 """Base class for all entities. 

2343 

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

2345 allocate memory. Examples of entities are types and record 

2346 objects. 

2347 

2348 :attribute name: unqualified name of the entity 

2349 :type: str 

2350 

2351 """ 

2352 def __init__(self, name, location): 

2353 # lobster-trace: LRM.Described_Name_Equality 

2354 super().__init__(location) 

2355 assert isinstance(name, str) 

2356 self.name = name 

2357 

2358 

2359class Typed_Entity(Entity, metaclass=ABCMeta): 

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

2361 

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

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

2364 are record objects and components. 

2365 

2366 :attribute n_typ: type of the entity 

2367 :type: Type 

2368 

2369 """ 

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

2371 # lobster-exclude: Constructor only declares variables 

2372 super().__init__(name, location) 

2373 assert isinstance(n_typ, Type) 

2374 self.n_typ = n_typ 

2375 

2376 

2377class Quantified_Variable(Typed_Entity): 

2378 """Variable used in quantified expression. 

2379 

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

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

2382 

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

2384 ^ 

2385 

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

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

2388 

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

2390 :type: Type 

2391 

2392 """ 

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

2394 # lobster-exclude: Debugging feature 

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

2396 self.n_typ.dump(indent + 1) 

2397 

2398 

2399class Type(Entity, metaclass=ABCMeta): 

2400 """Abstract base class for all types. 

2401 

2402 """ 

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

2404 assert isinstance(mh, Message_Handler) 

2405 assert isinstance(value, Expression) 

2406 assert isinstance(gstab, Symbol_Table) 

2407 return True 

2408 

2409 def get_example_value(self): 

2410 # lobster-exclude: utility method 

2411 assert False 

2412 

2413 

2414class Concrete_Type(Type, metaclass=ABCMeta): 

2415 # lobster-trace: LRM.Type_Declarations 

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

2417 

2418 :attribute n_package: package where this type was declared 

2419 :type: Package 

2420 """ 

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

2422 super().__init__(name, location) 

2423 assert isinstance(n_package, Package) 

2424 self.n_package = n_package 

2425 

2426 def fully_qualified_name(self): 

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

2428 

2429 :returns: the type's full name 

2430 :rtype: str 

2431 """ 

2432 return self.n_package.name + "." + self.name 

2433 

2434 def __hash__(self): 

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

2436 

2437 def __repr__(self): 

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

2439 self.fully_qualified_name()) 

2440 

2441 

2442class Builtin_Type(Type, metaclass=ABCMeta): 

2443 # lobster-trace: LRM.Builtin_Types 

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

2445 

2446 """ 

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

2448 

2449 def __init__(self, name): 

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

2451 

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

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

2454 

2455 

2456class Builtin_Numeric_Type(Builtin_Type, metaclass=ABCMeta): 

2457 # lobster-trace: LRM.Builtin_Types 

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

2459 

2460 """ 

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

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

2463 

2464 

2465class Builtin_Function(Entity): 

2466 # lobster-trace: LRM.Builtin_Functions 

2467 """Builtin functions. 

2468 

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

2470 

2471 :attribute arity: number of parameters 

2472 :type: int 

2473 

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

2475 :type: bool 

2476 

2477 """ 

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

2479 

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

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

2482 assert isinstance(arity, int) 

2483 assert isinstance(arity_at_least, bool) 

2484 assert arity >= 0 

2485 self.arity = arity 

2486 self.arity_at_least = arity_at_least 

2487 

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

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

2490 

2491 

2492class Array_Type(Type): 

2493 """Anonymous array type. 

2494 

2495 These are declared implicitly for each record component that has 

2496 an array specifier:: 

2497 

2498 foo Integer [5 .. *] 

2499 ^ 

2500 

2501 :attribute lower_bound: minimum number of elements 

2502 :type: int 

2503 

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

2505 :type: Location 

2506 

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

2508 :type: int 

2509 

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

2511 :type: Location 

2512 

2513 :attribute element_type: type of the array elements 

2514 :type: Type 

2515 

2516 """ 

2517 def __init__(self, 

2518 location, 

2519 element_type, 

2520 loc_lower, 

2521 lower_bound, 

2522 loc_upper, 

2523 upper_bound): 

2524 # lobster-exclude: Constructor only declares variables 

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

2526 assert isinstance(lower_bound, int) 

2527 assert lower_bound >= 0 

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

2529 assert upper_bound is None or upper_bound >= 0 

2530 assert isinstance(loc_lower, Location) 

2531 assert isinstance(loc_upper, Location) 

2532 

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

2534 name = "universal array" 

2535 elif upper_bound is None: 

2536 if lower_bound == 0: 

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

2538 else: 

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

2540 element_type.name) 

2541 elif lower_bound == upper_bound: 

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

2543 element_type.name) 

2544 else: 

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

2546 upper_bound, 

2547 element_type.name) 

2548 super().__init__(name, location) 

2549 self.lower_bound = lower_bound 

2550 self.loc_lower = loc_lower 

2551 self.upper_bound = upper_bound 

2552 self.loc_upper = loc_upper 

2553 self.element_type = element_type 

2554 

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

2556 # lobster-exclude: Debugging feature 

2557 self.write_indent(indent, "Array_Type") 

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

2559 if self.upper_bound is None: 

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

2561 else: 

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

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

2564 

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

2566 assert isinstance(mh, Message_Handler) 

2567 assert isinstance(gstab, Symbol_Table) 

2568 

2569 if isinstance(value, Array_Aggregate): 

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

2571 for v in value.value) 

2572 else: 

2573 assert isinstance(value, Implicit_Null) 

2574 return True 

2575 

2576 def get_example_value(self): 

2577 # lobster-exclude: utility method 

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

2579 

2580 

2581class Union_Type(Type): 

2582 # lobster-trace: LRM.union_type 

2583 # lobster-trace: LRM.Union_Type_Minimum_Members 

2584 # lobster-trace: LRM.Union_Type_Record_Types_Only 

2585 """Anonymous union type for record references. 

2586 

2587 These are declared implicitly when a record component specifies 

2588 multiple allowed record types using bracket syntax:: 

2589 

2590 parent [Systemrequirement, Codebeamerrequirement] 

2591 ^ 

2592 

2593 :attribute types: the allowed record types 

2594 :type: list[Record_Type] 

2595 

2596 """ 

2597 def __init__(self, location, types): 

2598 assert isinstance(types, list) 

2599 assert len(types) >= 1 

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

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

2602 super().__init__(name, location) 

2603 self.types = types 

2604 self._field_map = None 

2605 

2606 def get_field_map(self): 

2607 # lobster-trace: LRM.Union_Type_Field_Access 

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

2609 

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

2611 

2612 * ``component``: a representative Composite_Component 

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

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

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

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

2617 

2618 :rtype: dict[str, dict] 

2619 """ 

2620 if self._field_map is not None: 

2621 return self._field_map 

2622 

2623 field_map = {} 

2624 for record_type in self.types: 

2625 seen_in_type = set() 

2626 for comp in record_type.all_components(): 

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

2628 continue 

2629 seen_in_type.add(comp.name) 

2630 if comp.name not in field_map: 

2631 field_map[comp.name] = { 

2632 "component" : comp, 

2633 "n_typ" : comp.n_typ, 

2634 "count" : 1, 

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

2636 "optional_in_any" : comp.optional, 

2637 } 

2638 else: 

2639 info = field_map[comp.name] 

2640 info["count"] += 1 

2641 # Type identity (is) is correct here: 

2642 # non-union type objects are structural 

2643 # singletons in the symbol table, so 

2644 # identity comparison is both correct and 

2645 # cheap. 

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

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

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

2649 info["optional_in_any"] = True 

2650 

2651 self._field_map = field_map 

2652 return self._field_map 

2653 

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

2655 # lobster-exclude: Debugging feature 

2656 self.write_indent(indent, "Union_Type") 

2657 for t in self.types: 

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

2659 

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

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

2662 # happens in Record_Reference.resolve_references() via 

2663 # is_compatible(). Returning True unconditionally is 

2664 # intentional. 

2665 assert isinstance(mh, Message_Handler) 

2666 assert isinstance(value, Expression) 

2667 assert isinstance(gstab, Symbol_Table) 

2668 return True 

2669 

2670 def is_compatible(self, record_type): 

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

2672 

2673 :param record_type: type to check 

2674 :type record_type: Record_Type 

2675 

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

2677 :rtype: bool 

2678 """ 

2679 assert isinstance(record_type, Record_Type) 

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

2681 

2682 def get_example_value(self): 

2683 # lobster-exclude: utility method 

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

2685 

2686 

2687class Builtin_Integer(Builtin_Numeric_Type): 

2688 # lobster-trace: LRM.Builtin_Types 

2689 # lobster-trace: LRM.Integer_Values 

2690 """Builtin integer type.""" 

2691 def __init__(self): 

2692 super().__init__("Integer") 

2693 

2694 def get_example_value(self): 

2695 # lobster-exclude: utility method 

2696 return "100" 

2697 

2698 

2699class Builtin_Decimal(Builtin_Numeric_Type): 

2700 # lobster-trace: LRM.Builtin_Types 

2701 # lobster-trace: LRM.Decimal_Values 

2702 """Builtin decimal type.""" 

2703 def __init__(self): 

2704 super().__init__("Decimal") 

2705 

2706 def get_example_value(self): 

2707 # lobster-exclude: utility method 

2708 return "3.14" 

2709 

2710 

2711class Builtin_Boolean(Builtin_Type): 

2712 # lobster-trace: LRM.Builtin_Types 

2713 # lobster-trace: LRM.Boolean_Values 

2714 """Builtin boolean type.""" 

2715 def __init__(self): 

2716 super().__init__("Boolean") 

2717 

2718 def get_example_value(self): 

2719 # lobster-exclude: utility method 

2720 return "true" 

2721 

2722 

2723class Builtin_String(Builtin_Type): 

2724 # lobster-trace: LRM.Builtin_Types 

2725 # lobster-trace: LRM.String_Values 

2726 """Builtin string type.""" 

2727 def __init__(self): 

2728 super().__init__("String") 

2729 

2730 def get_example_value(self): 

2731 # lobster-exclude: utility method 

2732 return "\"potato\"" 

2733 

2734 

2735class Builtin_Markup_String(Builtin_String): 

2736 # lobster-trace: LRM.Builtin_Types 

2737 # lobster-trace: LRM.Markup_String_Values 

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

2739 objects. 

2740 """ 

2741 def __init__(self): 

2742 super().__init__() 

2743 self.name = "Markup_String" 

2744 

2745 def get_example_value(self): 

2746 # lobster-exclude: utility method 

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

2748 

2749 

2750class Package(Entity): 

2751 """Packages. 

2752 

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

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

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

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

2757 

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

2759 trlc file 

2760 :type: bool 

2761 

2762 :attribute symbols: symbol table of the package 

2763 :type: Symbol_Table 

2764 

2765 """ 

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

2767 # lobster-exclude: Constructor only declares variables 

2768 super().__init__(name, location) 

2769 assert isinstance(builtin_stab, Symbol_Table) 

2770 assert isinstance(declared_late, bool) 

2771 self.symbols = Symbol_Table() 

2772 self.symbols.make_visible(builtin_stab) 

2773 self.declared_late = declared_late 

2774 

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

2776 # lobster-exclude: Debugging feature 

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

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

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

2780 

2781 def __repr__(self): 

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

2783 self.name) 

2784 

2785 

2786class Composite_Type(Concrete_Type, metaclass=ABCMeta): 

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

2788 functionality. 

2789 

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

2791 :type: Symbol_Table[Composite_Component] 

2792 

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

2794 :type: str 

2795 

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

2797 inherited checks) 

2798 :type: list[Check] 

2799 

2800 """ 

2801 def __init__(self, 

2802 name, 

2803 description, 

2804 location, 

2805 package, 

2806 inherited_symbols=None): 

2807 # lobster-trace: LRM.Described_Name_Description 

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

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

2810 assert isinstance(inherited_symbols, Symbol_Table) or \ 

2811 inherited_symbols is None 

2812 

2813 self.components = Symbol_Table(inherited_symbols) 

2814 self.description = description 

2815 self.checks = [] 

2816 

2817 def add_check(self, n_check): 

2818 # lobster-trace: LRM.Check_Evaluation_Order 

2819 assert isinstance(n_check, Check) 

2820 self.checks.append(n_check) 

2821 

2822 def iter_checks(self): 

2823 # lobster-trace: LRM.Check_Evaluation_Order 

2824 yield from self.checks 

2825 

2826 def all_components(self): 

2827 # lobster-exclude: Convenience function 

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

2829 

2830 :rtype: list[Composite_Component] 

2831 """ 

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

2833 

2834 

2835class Composite_Component(Typed_Entity): 

2836 """Component in a record or tuple. 

2837 

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

2839 declared:: 

2840 

2841 type|tuple T { 

2842 foo "blah" optional Boolean 

2843 ^1 ^2 ^3 ^4 

2844 

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

2846 :type: str 

2847 

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

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

2850 :type: Composite_Type 

2851 

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

2853 :type: bool 

2854 

2855 """ 

2856 

2857 def __init__(self, 

2858 name, 

2859 description, 

2860 location, 

2861 member_of, 

2862 n_typ, 

2863 optional): 

2864 # lobster-trace: LRM.Described_Name_Description 

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

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

2867 assert isinstance(member_of, Composite_Type) 

2868 assert isinstance(optional, bool) 

2869 self.description = description 

2870 self.member_of = member_of 

2871 self.optional = optional 

2872 

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

2874 # lobster-exclude: Debugging feature 

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

2876 if self.description: 

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

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

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

2880 

2881 def __repr__(self): 

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

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

2884 self.name) 

2885 

2886 

2887class Record_Type(Composite_Type): 

2888 """A user-defined record type. 

2889 

2890 In this example:: 

2891 

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

2893 ^1 ^2 ^3 

2894 

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

2896 of the :class:`Composite_Type` base. 

2897 

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

2899 :type: Record_Type 

2900 

2901 :attribute frozen: mapping of frozen components 

2902 :type: dict[str, Expression] 

2903 

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

2905 :type: bool 

2906 

2907 :attribute is_abstract: type is abstract 

2908 :type: bool 

2909 

2910 """ 

2911 def __init__(self, 

2912 name, 

2913 description, 

2914 location, 

2915 package, 

2916 n_parent, 

2917 is_abstract): 

2918 # lobster-exclude: Constructor only declares variables 

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

2920 assert isinstance(is_abstract, bool) 

2921 super().__init__(name, 

2922 description, 

2923 location, 

2924 package, 

2925 n_parent.components if n_parent else None) 

2926 self.parent = n_parent 

2927 self.frozen = {} 

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

2929 self.is_abstract = is_abstract 

2930 

2931 def iter_checks(self): 

2932 # lobster-trace: LRM.Check_Evaluation_Order 

2933 # lobster-trace: LRM.Check_Evaluation_Order_For_Extensions 

2934 if self.parent: 

2935 yield from self.parent.iter_checks() 

2936 yield from self.checks 

2937 

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

2939 # lobster-exclude: Debugging feature 

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

2941 if self.description: 

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

2943 if self.parent: 

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

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

2946 if self.checks: 

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

2948 for n_check in self.checks: 

2949 n_check.dump(indent + 2) 

2950 else: 

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

2952 

2953 def all_components(self): 

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

2955 

2956 :rtype: list[Composite_Component] 

2957 """ 

2958 if self.parent: 

2959 return self.parent.all_components() + \ 

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

2961 else: 

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

2963 

2964 def is_subclass_of(self, record_type): 

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

2966 

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

2968 :type record_type: Record_Type 

2969 

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

2971 :rtype: Boolean 

2972 """ 

2973 assert isinstance(record_type, Record_Type) 

2974 

2975 ptr = self 

2976 while ptr: 

2977 if ptr is record_type: 

2978 return True 

2979 else: 

2980 ptr = ptr.parent 

2981 return False 

2982 

2983 def is_frozen(self, n_component): 

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

2985 

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

2987 (or any of its parents) 

2988 :type n_component: Composite_Component 

2989 

2990 :rtype: bool 

2991 """ 

2992 assert isinstance(n_component, Composite_Component) 

2993 if n_component.name in self.frozen: 

2994 return True 

2995 elif self.parent: 

2996 return self.parent.is_frozen(n_component) 

2997 else: 

2998 return False 

2999 

3000 def get_freezing_expression(self, n_component): 

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

3002 

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

3004 component that his not frozen. 

3005 

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

3007 (or any of its parents) 

3008 :type n_component: Composite_Component 

3009 

3010 :rtype: Expression 

3011 

3012 """ 

3013 assert isinstance(n_component, Composite_Component) 

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

3015 return self.frozen[n_component.name] 

3016 elif self.parent: 

3017 return self.parent.get_freezing_expression(n_component) 

3018 else: 

3019 assert False 

3020 

3021 def get_example_value(self): 

3022 # lobster-exclude: utility method 

3023 return "%s_instance" % self.name 

3024 

3025 

3026class Tuple_Type(Composite_Type): 

3027 """A user-defined tuple type. 

3028 

3029 In this example:: 

3030 

3031 tuple T "optional description of T" { 

3032 ^1 ^2 

3033 

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

3035 of the :class:`Composite_Type` base. 

3036 

3037 :attribute separators: list of syntactic separators. 

3038 :type: list[Separator] 

3039 

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

3041 precisely one less separator than components. 

3042 

3043 """ 

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

3045 # lobster-trace: LRM.Tuple_Declaration 

3046 super().__init__(name, 

3047 description, 

3048 location, 

3049 package) 

3050 self.separators = [] 

3051 

3052 def add_separator(self, n_separator): 

3053 # lobster-exclude: utility method 

3054 assert isinstance(n_separator, Separator) 

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

3056 self.separators.append(n_separator) 

3057 

3058 def iter_separators(self): 

3059 """Iterate over all separators""" 

3060 # lobster-exclude: utility method 

3061 yield from self.separators 

3062 

3063 def iter_sequence(self): 

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

3065 # lobster-exclude: utility method 

3066 if self.separators: 

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

3068 yield n_component 

3069 if i < len(self.separators): 

3070 yield self.separators[i] 

3071 else: 

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

3073 

3074 def has_separators(self): 

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

3076 # lobster-exclude: utility method 

3077 return bool(self.separators) 

3078 

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

3080 # lobster-exclude: Debugging feature 

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

3082 if self.description: 

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

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

3085 for n_item in self.iter_sequence(): 

3086 n_item.dump(indent + 2) 

3087 if self.checks: 

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

3089 for n_check in self.checks: 

3090 n_check.dump(indent + 2) 

3091 else: 

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

3093 

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

3095 # lobster-trace: LRM.Check_Evaluation_Order 

3096 assert isinstance(mh, Message_Handler) 

3097 assert isinstance(gstab, Symbol_Table) 

3098 

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

3100 ok = True 

3101 for check in self.iter_checks(): 

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

3103 ok = False 

3104 return ok 

3105 else: 

3106 assert isinstance(value, Implicit_Null) 

3107 return True 

3108 

3109 def get_example_value(self): 

3110 # lobster-exclude: utility method 

3111 parts = [] 

3112 for n_item in self.iter_sequence(): 

3113 if isinstance(n_item, Composite_Component): 

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

3115 else: 

3116 parts.append(n_item.to_string()) 

3117 if self.has_separators(): 

3118 return " ".join(parts) 

3119 else: 

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

3121 

3122 

3123class Separator(Node): 

3124 # lobster-trace: LRM.Tuple_Declaration 

3125 """User-defined syntactic separator 

3126 

3127 For example:: 

3128 

3129 separator x 

3130 ^1 

3131 

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

3133 :type: Token 

3134 """ 

3135 def __init__(self, token): 

3136 super().__init__(token.location) 

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

3138 "AT", 

3139 "COLON", 

3140 "SEMICOLON") 

3141 self.token = token 

3142 

3143 def to_string(self): 

3144 return { 

3145 "AT" : "@", 

3146 "COLON" : ":", 

3147 "SEMICOLON" : ";" 

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

3149 

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

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

3152 

3153 

3154class Enumeration_Type(Concrete_Type): 

3155 """User-defined enumeration types. 

3156 

3157 For example:: 

3158 

3159 enum T "potato" { 

3160 ^1 ^2 

3161 

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

3163 :type: str 

3164 

3165 :attribute literals: the literals in this enumeration 

3166 :type: Symbol_Table[Enumeration_Literal_Spec] 

3167 

3168 """ 

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

3170 # lobster-trace: LRM.Described_Name_Description 

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

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

3173 self.literals = Symbol_Table() 

3174 self.description = description 

3175 

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

3177 # lobster-exclude: Debugging feature 

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

3179 if self.description: 

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

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

3182 

3183 def get_example_value(self): 

3184 # lobster-exclude: utility method 

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

3186 if options: 

3187 choice = len(options) // 2 

3188 return self.name + "." + choice.name 

3189 else: 

3190 return "ERROR" 

3191 

3192 

3193class Enumeration_Literal_Spec(Typed_Entity): 

3194 """Declared literal in an enumeration declaration. 

3195 

3196 Note that for literals mentioned later in record object 

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

3198 are used here:: 

3199 

3200 enum ASIL { 

3201 QM "not safety related" 

3202 ^1 ^2 

3203 

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

3205 :type: str 

3206 

3207 """ 

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

3209 # lobster-trace: LRM.Described_Name_Description 

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

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

3212 assert isinstance(enum, Enumeration_Type) 

3213 self.description = description 

3214 

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

3216 # lobster-exclude: Debugging feature 

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

3218 if self.description: 

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

3220 

3221 

3222class Record_Object(Typed_Entity): 

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

3224 

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

3226 

3227 section "Potato" { 

3228 ^5 

3229 Requirement PotatoReq { 

3230 ^1 ^2 

3231 component1 = 42 

3232 ^3 ^4 

3233 

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

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

3236 class. 

3237 

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

3239 :type: dict[str, Expression] 

3240 

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

3242 :type: Section 

3243 

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

3245 :type: Section 

3246 

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

3248 to: 

3249 

3250 * :class:`Literal` 

3251 * :class:`Unary_Expression` 

3252 * :class:`Array_Aggregate` 

3253 * :class:`Tuple_Aggregate` 

3254 * :class:`Record_Reference` 

3255 * :class:`Implicit_Null` 

3256 

3257 """ 

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

3259 # lobster-trace: LRM.Section_Declaration 

3260 # lobster-trace: LRM.Unspecified_Optional_Components 

3261 # lobster-trace: LRM.Record_Object_Declaration 

3262 

3263 assert isinstance(n_typ, Record_Type) 

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

3265 assert isinstance(n_package, Package) 

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

3267 self.field = { 

3268 comp.name: Implicit_Null(self, comp) 

3269 for comp in self.n_typ.all_components() 

3270 } 

3271 self.section = section 

3272 self.n_package = n_package 

3273 

3274 def fully_qualified_name(self): 

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

3276 

3277 :returns: the object's full name 

3278 :rtype: str 

3279 """ 

3280 return self.n_package.name + "." + self.name 

3281 

3282 def to_python_dict(self): 

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

3284 

3285 For example it might provide:: 

3286 

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

3288 "bar" : None, 

3289 "baz" : "value"} 

3290 

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

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

3293 

3294 """ 

3295 return {name: value.to_python_object() 

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

3297 

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

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

3300 

3301 def assign(self, component, value): 

3302 assert isinstance(component, Composite_Component) 

3303 assert isinstance(value, (Literal, 

3304 Array_Aggregate, 

3305 Tuple_Aggregate, 

3306 Record_Reference, 

3307 Implicit_Null, 

3308 Unary_Expression)), \ 

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

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

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

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

3313 self.field[component.name] = value 

3314 

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

3316 # lobster-exclude: Debugging feature 

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

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

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

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

3321 value.dump(indent + 2) 

3322 if self.section: 

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

3324 

3325 def resolve_references(self, mh): 

3326 assert isinstance(mh, Message_Handler) 

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

3328 val.resolve_references(mh) 

3329 

3330 def perform_checks(self, mh, gstab): 

3331 # lobster-trace: LRM.Check_Evaluation_Order 

3332 # lobster-trace: LRM.Evaluation_Of_Checks 

3333 assert isinstance(mh, Message_Handler) 

3334 assert isinstance(gstab, Symbol_Table) 

3335 

3336 ok = True 

3337 

3338 # First evaluate all tuple checks 

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

3340 if not n_comp.n_typ.perform_type_checks(mh, 

3341 self.field[n_comp.name], 

3342 gstab): 

3343 ok = False 

3344 

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

3346 

3347 # Then evaluate all record checks 

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

3349 # Prints messages, if applicable. Raises exception on 

3350 # fatal checks, which causes this to abort. 

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

3352 ok = False 

3353 

3354 return ok 

3355 

3356 def __repr__(self): 

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

3358 self.n_package.name + "." + 

3359 self.n_typ.name + "." + 

3360 self.name) 

3361 

3362 

3363class Section(Entity): 

3364 # lobster-trace: LRM.Section_Declaration 

3365 """A section for readability 

3366 

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

3368 objects together:: 

3369 

3370 section "Foo" { 

3371 ^^^^^ parent section 

3372 section "Bar" { 

3373 ^^^^^ section 

3374 

3375 :attribute parent: the parent section or None 

3376 :type: Section 

3377 

3378 """ 

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

3380 super().__init__(name, location) 

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

3382 self.parent = parent 

3383 

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

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

3386 if self.parent is None: 

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

3388 else: 

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

3390 

3391 

3392############################################################################## 

3393# Symbol Table & Scopes 

3394############################################################################## 

3395 

3396class Symbol_Table: 

3397 """ Symbol table mapping names to entities 

3398 """ 

3399 def __init__(self, parent=None): 

3400 # lobster-exclude: Constructor only declares variables 

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

3402 self.parent = parent 

3403 self.imported = [] 

3404 self.table = OrderedDict() 

3405 self.trlc_files = [] 

3406 self.section_names = [] 

3407 

3408 @staticmethod 

3409 def simplified_name(name): 

3410 # lobster-trace: LRM.Sufficiently_Distinct 

3411 assert isinstance(name, str) 

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

3413 

3414 def all_names(self): 

3415 # lobster-exclude: API for users 

3416 """ All names in the symbol table 

3417 

3418 :rtype: set[str] 

3419 """ 

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

3421 if self.parent: 

3422 rv |= self.parent.all_names() 

3423 return rv 

3424 

3425 def iter_record_objects_by_section(self): 

3426 """API for users 

3427 

3428 Retriving information about the section hierarchy for record objects 

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

3430 sub sections and record objects 

3431 Output: Information about sections and level of sections, 

3432 record objects and levels of record object 

3433 """ 

3434 for record_object in self.iter_record_objects(): 

3435 location = record_object.location.file_name 

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

3437 self.trlc_files.append(location) 

3438 yield location 

3439 if record_object.section: 

3440 object_level = len(record_object.section) - 1 

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

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

3443 self.section_names.append(section) 

3444 yield section.name, level 

3445 yield record_object, object_level 

3446 else: 

3447 object_level = 0 

3448 yield record_object, object_level 

3449 

3450 def iter_record_objects(self): 

3451 # lobster-exclude: API for users 

3452 """ Iterate over all record objects 

3453 

3454 :rtype: iterable[Record_Object] 

3455 """ 

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

3457 if isinstance(item, Package): 

3458 yield from item.symbols.iter_record_objects() 

3459 

3460 elif isinstance(item, Record_Object): 

3461 yield item 

3462 

3463 def values(self, subtype=None): 

3464 # lobster-exclude: API for users 

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

3466 if self.parent: 

3467 yield from self.parent.values(subtype) 

3468 for name in sorted(self.table): 

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

3470 yield self.table[name] 

3471 

3472 def make_visible(self, stab): 

3473 assert isinstance(stab, Symbol_Table) 

3474 self.imported.append(stab) 

3475 

3476 def register(self, mh, entity): 

3477 # lobster-trace: LRM.Duplicate_Types 

3478 # lobster-trace: LRM.Unique_Enumeration_Literals 

3479 # lobster-trace: LRM.Tuple_Unique_Field_Names 

3480 # lobster-trace: LRM.Sufficiently_Distinct 

3481 # lobster-trace: LRM.Unique_Object_Names 

3482 

3483 assert isinstance(mh, Message_Handler) 

3484 assert isinstance(entity, Entity) 

3485 

3486 simple_name = self.simplified_name(entity.name) 

3487 

3488 if self.contains_raw(simple_name): 

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

3490 simplified=True) 

3491 if pdef.name == entity.name: 

3492 mh.error(entity.location, 

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

3494 mh.cross_file_reference(pdef.location)) 

3495 else: 

3496 mh.error(entity.location, 

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

3498 (entity.name, 

3499 pdef.name, 

3500 mh.cross_file_reference(pdef.location))) 

3501 

3502 else: 

3503 self.table[simple_name] = entity 

3504 

3505 def __contains__(self, name): 

3506 # lobster-trace: LRM.Described_Name_Equality 

3507 return self.contains(name) 

3508 

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

3510 # lobster-trace: LRM.Described_Name_Equality 

3511 # lobster-trace: LRM.Sufficiently_Distinct 

3512 # 

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

3514 # table. 

3515 assert isinstance(simple_name, str) 

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

3517 

3518 if simple_name in self.table: 

3519 # No need to continue searching since registering a 

3520 # clashing name would have been stopped 

3521 return precise_name is None or \ 

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

3523 

3524 elif self.parent: 

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

3526 

3527 for stab in self.imported: 

3528 if stab.contains_raw(simple_name, precise_name): 

3529 return True 

3530 

3531 return False 

3532 

3533 def contains(self, name): 

3534 # lobster-trace: LRM.Described_Name_Equality 

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

3536 

3537 :param name: the name to test 

3538 :type name: str 

3539 

3540 :rtype: bool 

3541 """ 

3542 assert isinstance(name, str) 

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

3544 

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

3546 # lobster-trace: LRM.Described_Name_Equality 

3547 # lobster-trace: LRM.Sufficiently_Distinct 

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

3549 

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

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

3552 

3553 :param mh: The message handler to use 

3554 :type mh: Message_Handler 

3555 

3556 :param name: The name to search for 

3557 :type name: str 

3558 

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

3560 is not an instance of the given class 

3561 :type required_subclass: type 

3562 

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

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

3565 :rtype: Entity 

3566 

3567 """ 

3568 assert isinstance(mh, Message_Handler) 

3569 assert isinstance(name, str) 

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

3571 

3572 simple_name = self.simplified_name(name) 

3573 

3574 ptr = self 

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

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

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

3578 rv = ptr.table[simple_name] 

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

3580 return None 

3581 

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

3583 not isinstance(rv, required_subclass): 

3584 mh.error(rv.location, 

3585 "%s %s is not a %s" % 

3586 (rv.__class__.__name__, 

3587 name, 

3588 required_subclass.__name__)) 

3589 return rv 

3590 else: 

3591 ptr = ptr.parent 

3592 

3593 return None 

3594 

3595 def lookup_direct(self, 

3596 mh, 

3597 name, 

3598 error_location, 

3599 required_subclass=None, 

3600 simplified=False): 

3601 # lobster-trace: LRM.Described_Name_Equality 

3602 # lobster-trace: LRM.Sufficiently_Distinct 

3603 # lobster-trace: LRM.Valid_Base_Names 

3604 # lobster-trace: LRM.Valid_Access_Prefixes 

3605 # lobster-trace: LRM.Valid_Function_Prefixes 

3606 """Retrieve an object from the table 

3607 

3608 For example:: 

3609 

3610 pkg = stab.lookup_direct(mh, 

3611 "potato", 

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

3613 Package) 

3614 

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

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

3617 Package, then the following error is issued:: 

3618 

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

3620 

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

3622 

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

3624 

3625 :param mh: The message handler to use 

3626 :type mh: Message_Handler 

3627 

3628 :param name: The name to search for 

3629 :type name: str 

3630 

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

3632 not found 

3633 :type error_location: Location 

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 :param simplified: If set, look up the given simplified name instead \ 

3640 of the actual name 

3641 :type simplified: bool 

3642 

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

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

3645 :returns: the specified entity 

3646 :rtype: Entity 

3647 

3648 """ 

3649 assert isinstance(mh, Message_Handler) 

3650 assert isinstance(name, str) 

3651 assert isinstance(error_location, Location) 

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

3653 assert isinstance(simplified, bool) 

3654 

3655 simple_name = self.simplified_name(name) 

3656 ptr = self 

3657 options = [] 

3658 

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

3660 while ptr: 

3661 if simple_name in ptr.table: 

3662 rv = ptr.table[simple_name] 

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

3664 mh.error(error_location, 

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

3666 (name, 

3667 rv.name)) 

3668 

3669 if required_subclass is not None and \ 

3670 not isinstance(rv, required_subclass): 

3671 mh.error(error_location, 

3672 "%s %s is not a %s" % 

3673 (rv.__class__.__name__, 

3674 name, 

3675 required_subclass.__name__)) 

3676 return rv 

3677 else: 

3678 options += list(item.name 

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

3680 ptr = ptr.parent 

3681 

3682 matches = get_close_matches( 

3683 word = name, 

3684 possibilities = options, 

3685 n = 1) 

3686 

3687 if matches: 

3688 mh.error(error_location, 

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

3690 (name, 

3691 matches[0])) 

3692 else: 

3693 mh.error(error_location, 

3694 "unknown symbol %s" % name) 

3695 

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

3697 # lobster-trace: LRM.Described_Name_Equality 

3698 assert isinstance(mh, Message_Handler) 

3699 assert isinstance(referencing_token, Token) 

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

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

3702 

3703 return self.lookup_direct( 

3704 mh = mh, 

3705 name = referencing_token.value, 

3706 error_location = referencing_token.location, 

3707 required_subclass = required_subclass) 

3708 

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

3710 # lobster-exclude: Debugging feature 

3711 assert isinstance(indent, int) 

3712 assert indent >= 0 

3713 assert isinstance(message, str) 

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

3715 

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

3717 # lobster-exclude: Debugging feature 

3718 if omit_heading: 

3719 new_indent = indent 

3720 else: 

3721 self.write_indent(indent, "Symbol_Table") 

3722 new_indent = indent + 1 

3723 ptr = self 

3724 while ptr: 

3725 for name in ptr.table: 

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

3727 ptr = ptr.parent 

3728 

3729 @classmethod 

3730 def create_global_table(cls, mh): 

3731 # lobster-trace: LRM.Builtin_Types 

3732 # lobster-trace: LRM.Builtin_Functions 

3733 # lobster-trace: LRM.Builtin_Type_Conversion_Functions 

3734 # lobster-trace: LRM.Signature_Len 

3735 # lobster-trace: LRM.Signature_String_End_Functions 

3736 # lobster-trace: LRM.Signature_Matches 

3737 

3738 stab = Symbol_Table() 

3739 stab.register(mh, Builtin_Integer()) 

3740 stab.register(mh, Builtin_Decimal()) 

3741 stab.register(mh, Builtin_Boolean()) 

3742 stab.register(mh, Builtin_String()) 

3743 stab.register(mh, Builtin_Markup_String()) 

3744 stab.register(mh, 

3745 Builtin_Function("len", 1)) 

3746 stab.register(mh, 

3747 Builtin_Function("startswith", 2)) 

3748 stab.register(mh, 

3749 Builtin_Function("endswith", 2)) 

3750 stab.register(mh, 

3751 Builtin_Function("matches", 2)) 

3752 stab.register(mh, 

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

3754 

3755 return stab 

3756 

3757 

3758class Scope: 

3759 def __init__(self): 

3760 # lobster-exclude: Constructor only declares variables 

3761 self.scope = [] 

3762 

3763 def push(self, stab): 

3764 assert isinstance(stab, Symbol_Table) 

3765 self.scope.append(stab) 

3766 

3767 def pop(self): 

3768 self.scope.pop() 

3769 

3770 def contains(self, name): 

3771 assert isinstance(name, str) 

3772 

3773 for stab in reversed(self.scope): 

3774 if stab.contains(name): 

3775 return True 

3776 return False 

3777 

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

3779 assert len(self.scope) >= 1 

3780 assert isinstance(mh, Message_Handler) 

3781 assert isinstance(referencing_token, Token) 

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

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

3784 

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

3786 if stab.contains(referencing_token.value): 

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

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

3789 

3790 def size(self): 

3791 return len(self.scope)