Coverage for trlc/ast.py: 91%

1363 statements  

« prev     ^ index     » next       coverage.py v7.16.2, created at 2026-09-30 11:03 +0000

1#!/usr/bin/env python3 

2# 

3# TRLC - Treat Requirements Like Code 

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

5# Copyright (C) 2024-2025 Florian Schanda 

6# 

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

8# 

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

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

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

12# (at your option) any later version. 

13# 

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

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

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

17# License for more details. 

18# 

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

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

21 

22from abc import ABCMeta, abstractmethod 

23import re 

24 

25from copy import copy 

26from difflib import get_close_matches 

27from enum import Enum, auto 

28from collections import OrderedDict 

29from fractions import Fraction 

30 

31from trlc.errors import TRLC_Error, Location, Message_Handler 

32from trlc.lexer import Token 

33from trlc import math 

34 

35# 

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

37# reference implementation. There are four sections: 

38# 

39# - Valuations deal with concrete values for record objects 

40# - AST expressions deal with the syntax tree 

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

42# - Symbol_Table and scope deals with name resolution 

43# 

44 

45 

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

47# Valuations 

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

49 

50 

51class Value: 

52 # lobster-trace: LRM.Boolean_Values 

53 # lobster-trace: LRM.Integer_Values 

54 # lobster-trace: LRM.Decimal_Values 

55 # lobster-trace: LRM.String_Values 

56 # lobster-trace: LRM.Markup_String_Values 

57 """Polymorphic value for evaluating expressions. 

58 

59 Any record references will be fully resolved. 

60 

61 :attribute location: source location this value comes from 

62 :type: Location 

63 

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

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

66 Record_Reference, Enumeration_Literal_Spec 

67 

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

69 :type: Type 

70 """ 

71 

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

73 assert isinstance(location, Location) 

74 assert value is None or isinstance( 

75 value, 

76 ( 

77 str, 

78 int, 

79 bool, 

80 list, # for arrays 

81 dict, # for tuples 

82 Fraction, 

83 Record_Reference, 

84 Enumeration_Literal_Spec, 

85 ), 

86 ) 

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

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

89 

90 self.location = location 

91 self.value = value 

92 self.typ = typ 

93 

94 def __eq__(self, other): 

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

96 

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

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

99 

100 def resolve_references(self, mh): 

101 assert isinstance(mh, Message_Handler) 

102 

103 if isinstance(self.value, Record_Reference): 

104 self.value.resolve(mh) 

105 

106 

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

108# AST Nodes 

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

110 

111 

112class Node(metaclass=ABCMeta): 

113 """Base class for all AST items. 

114 

115 :attribute location: source location 

116 :type: Location 

117 """ 

118 

119 def __init__(self, location): 

120 # lobster-exclude: Constructor only declares variables 

121 assert isinstance(location, Location) 

122 self.location = location 

123 

124 def set_ast_link(self, tok): 

125 assert isinstance(tok, Token) 

126 tok.ast_link = self 

127 

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

129 # lobster-exclude: Debugging feature 

130 assert isinstance(indent, int) 

131 assert indent >= 0 

132 assert isinstance(message, str) 

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

134 

135 @abstractmethod 

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

137 """Visualise the parse tree. 

138 

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

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

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

142 output like this:: 

143 

144 Symbol_Table 

145 Builtin_Boolean 

146 Builtin_Integer 

147 Builtin_Decimal 

148 Builtin_String 

149 Builtin_Markup_String 

150 Package bar 

151 Symbol_Table 

152 Record_Type MyType 

153 Composite_Component name 

154 Optional: False 

155 Type: String 

156 Checks 

157 Error 'description is too short' 

158 Anchor: description 

159 Binary Binary_Operator.COMP_GT Expression 

160 Type: Boolean 

161 Unary Unary_Operator.STRING_LENGTH Expression 

162 Type: Integer 

163 Name Reference to description 

164 Integer Literal 10 

165 Package instances 

166 Symbol_Table 

167 Record_Object SomeThing 

168 Type: MyType 

169 Field description: "Potato" 

170 Builtin_Function endswith 

171 Builtin_Function len 

172 Builtin_Function matches 

173 Builtin_Function startswith 

174 Builtin_Function oneof 

175 

176 """ 

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

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

179 # lobster-exclude: Debugging feature 

180 

181 

182class Check_Block(Node): 

183 """Node representing check blocks 

184 

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

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

187 

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

189 :type: Composite_Type 

190 

191 :attribute checks: list of checks 

192 :type: list[Check] 

193 

194 """ 

195 

196 def __init__(self, location, n_typ): 

197 # lobster-trace: LRM.Check_Block 

198 super().__init__(location) 

199 assert isinstance(n_typ, Composite_Type) 

200 self.n_typ = n_typ 

201 self.checks = [] 

202 

203 def add_check(self, n_check): 

204 # lobster-trace: LRM.Check_Evaluation_Order 

205 assert isinstance(n_check, Check) 

206 self.checks.append(n_check) 

207 

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

209 # lobster-exclude: Debugging feature 

210 self.write_indent(indent, "Check_Block") 

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

212 for n_check in self.checks: 

213 n_check.dump(indent + 1) 

214 

215 

216class Compilation_Unit(Node): 

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

218 

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

220 :type: Package 

221 

222 :attribute imports: packages explicitly imported by this file 

223 :type: set[Package] 

224 

225 :attribute wildcard_roots: roots of wildcard imports (``import foo.*``) 

226 :type: set[Package] 

227 

228 :attribute raw_imports: unresolved imports as (name, location, \ 

229 is_wildcard, tokens) 

230 :type: list[tuple[str, Location, bool, list[Token]]] 

231 

232 :attribute referenced_imports: imports actually used in this file 

233 :type: set[Package] 

234 

235 :attribute items: list of 

236 :type: list[Node] 

237 

238 """ 

239 

240 def __init__(self, file_name): 

241 # lobster-exclude: Constructor only declares variables 

242 super().__init__(Location(file_name)) 

243 self.package = None 

244 # Both default to empty sets (rather than None) so is_visible() 

245 # behaves consistently even if called before resolve_imports() 

246 # has run (e.g. only self-visibility is granted at that point). 

247 self.imports = set() 

248 self.wildcard_roots = set() 

249 # list of (name : str, location : Location, is_wildcard : bool, 

250 # tokens : list[Token]) 

251 self.raw_imports = [] 

252 self.referenced_imports = set() 

253 self.items = [] 

254 

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

256 # lobster-exclude: Debugging feature 

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

258 for name, _location, is_wildcard, _tokens in self.raw_imports: 

259 suffix = ".*" if is_wildcard else "" 

260 self.write_indent(indent + 1, f"Import: {name}{suffix}") 

261 for n_item in self.items: 

262 n_item.dump(indent + 1) 

263 

264 def set_package(self, pkg): 

265 # lobster-trace: LRM.Current_Package 

266 assert isinstance(pkg, Package) 

267 self.package = pkg 

268 

269 def add_import(self, mh, name, location, is_wildcard=False, tokens=None): 

270 # lobster-trace: LRM.Import_Visibility 

271 # lobster-trace: LRM.Self_Imports 

272 # lobster-trace: LRM.Wildcard_Import 

273 # lobster-trace: LRM.Wildcard_Self_Cover 

274 assert isinstance(mh, Message_Handler) 

275 assert isinstance(name, str) 

276 assert isinstance(location, Location) 

277 assert isinstance(is_wildcard, bool) 

278 assert tokens is None or isinstance(tokens, list) 

279 if tokens is None: 279 ↛ 280line 279 didn't jump to line 280 because the condition on line 279 was never true

280 tokens = [] 

281 

282 # An explicit self-import is an error. A wildcard whose root 

283 # covers the current package is permitted: the current package 

284 # stays implicitly visible (Wildcard_Self_Cover). 

285 if name == self.package.name and not is_wildcard: 

286 mh.error( 

287 location, 

288 "package %s cannot import itself" % self.package.name, 

289 explanation="a package always has access to its own " 

290 "types and objects; remove this import statement", 

291 ) 

292 

293 # Skip duplicates (same name and same wildcard flavour) 

294 for prev_name, _prev_location, prev_wildcard, _prev_tokens in self.raw_imports: 

295 if prev_name == name and prev_wildcard == is_wildcard: 

296 mh.warning( 

297 location, 

298 "duplicate import of package %s%s" 

299 % (name, ".*" if is_wildcard else ""), 

300 explanation="remove this redundant import " 

301 "statement, the package is already imported", 

302 ) 

303 return 

304 

305 self.raw_imports.append((name, location, is_wildcard, tokens)) 

306 

307 def resolve_imports(self, mh, stab): 

308 # lobster-trace: LRM.Import_Visibility 

309 # lobster-trace: LRM.Wildcard_Import 

310 assert isinstance(mh, Message_Handler) 

311 assert isinstance(stab, Symbol_Table) 

312 self.imports = set() 

313 self.wildcard_roots = set() 

314 for name, location, is_wildcard, tokens in self.raw_imports: 

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

316 # generate more error later. 

317 try: 

318 a_import = stab.lookup_direct(mh, name, location, Package) 

319 except TRLC_Error: 

320 continue 

321 # Link the import clause's tokens to the resolved package, 

322 # so tooling (e.g. go-to-definition) can navigate to it. 

323 for token in tokens: 

324 a_import.set_ast_link(token) 

325 if is_wildcard: 

326 self.wildcard_roots.add(a_import) 

327 else: 

328 self.imports.add(a_import) 

329 

330 def covered_by_wildcard(self, pkg): 

331 # lobster-trace: LRM.Wildcard_Import 

332 # A package is covered by a wildcard root if it is the root 

333 # itself or any descendant of it. 

334 assert isinstance(pkg, Package) 

335 for root in self.wildcard_roots: 

336 if pkg == root or pkg.name.startswith(root.name + "."): 336 ↛ 335line 336 didn't jump to line 335 because the condition on line 336 was always true

337 return root 

338 return None 

339 

340 def mark_import_used(self, pkg): 

341 """Record that a package was actually referenced in this file. 

342 

343 Called during qualified-name resolution so the lint pass can 

344 detect unused imports. If the package is visible through a 

345 wildcard import, the wildcard root is marked used as well. A 

346 reference to the current package itself never counts as using a 

347 wildcard import, even if the wildcard's root covers the current 

348 package (see LRM.Wildcard_Self_Cover): the current package is 

349 always implicitly visible, not "imported" by the wildcard. 

350 

351 :param pkg: the package that was used 

352 :type pkg: Package 

353 """ 

354 assert isinstance(pkg, Package) 

355 self.referenced_imports.add(pkg) 

356 if pkg is self.package: 

357 return 

358 root = self.covered_by_wildcard(pkg) 

359 if root is not None: 

360 self.referenced_imports.add(root) 

361 

362 def is_visible(self, n_pkg): 

363 # lobster-trace: LRM.Import_Visibility 

364 # lobster-trace: LRM.Wildcard_Import 

365 # lobster-trace: LRM.Nested_Visibility 

366 assert isinstance(n_pkg, Package) 

367 if n_pkg == self.package or n_pkg in self.imports: 

368 return True 

369 return self.covered_by_wildcard(n_pkg) is not None 

370 

371 def add_item(self, node): 

372 # lobster-trace: LRM.RSL_File 

373 # lobster-trace: LRM.TRLC_File 

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

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

376 ) 

377 self.items.append(node) 

378 

379 

380class Check(Node): 

381 """User defined check 

382 

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

384 

385 checks T { 

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

387 ^^^^^^^^^^^^^^^^^^^^^^^1 ^2 ^3 ^4 

388 

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

390 :type: Composite_Type 

391 

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

393 :type: Expression 

394 

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

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

397 :type: Composite_Component 

398 

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

400 not specified the default is 'error') 

401 :type: str 

402 

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

404 :type: str 

405 """ 

406 

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

408 # lobster-trace: LRM.Check_Block 

409 assert isinstance(n_type, Composite_Type) 

410 assert isinstance(n_expr, Expression) 

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

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

413 assert isinstance(t_message, Token) 

414 assert t_message.kind == "STRING" 

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

416 super().__init__(t_message.location) 

417 

418 self.n_type = n_type 

419 self.n_expr = n_expr 

420 self.n_anchor = n_anchor 

421 self.severity = severity 

422 # lobster-trace: LRM.No_Newlines_In_Message 

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

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

425 # raised is non-fatal. 

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

427 self.extrainfo = extrainfo 

428 self._uses_field_access = None 

429 

430 @property 

431 def uses_field_access(self): 

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

433 reference? 

434 

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

436 :class:`Field_Access_Expression` whose prefix has a 

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

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

439 Phase B ("after references"). 

440 

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

442 :rtype: bool 

443 

444 """ 

445 if self._uses_field_access is None: 

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

447 return self._uses_field_access 

448 

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

450 # lobster-exclude: Debugging feature 

451 if self.severity == "warning": 

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

453 elif self.severity == "error": 

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

455 else: 

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

457 if self.n_anchor: 

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

459 self.n_expr.dump(indent + 1) 

460 

461 def get_real_location(self, composite_object): 

462 # lobster-exclude: LRM.Anchoring 

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

464 if isinstance(composite_object, Record_Object): 

465 fields = composite_object.field 

466 else: 

467 fields = composite_object.value 

468 

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

470 return composite_object.location 

471 else: 

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

473 

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

475 # lobster-trace: LRM.Check_Messages 

476 # lobster-trace: LRM.Check_Severity 

477 assert isinstance(mh, Message_Handler) 

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

479 assert isinstance(gstab, Symbol_Table) 

480 

481 if isinstance(composite_object, Record_Object): 

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

483 else: 

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

485 if result.value is None: 

486 loc = self.get_real_location(composite_object) 

487 mh.error( 

488 loc, 

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

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

491 ) 

492 

493 assert isinstance(result.value, bool) 

494 

495 if not result.value: 

496 loc = self.get_real_location(composite_object) 

497 if self.severity == "warning": 

498 mh.warning( 

499 location=loc, 

500 message=self.message, 

501 explanation=self.extrainfo, 

502 user=True, 

503 ) 

504 else: 

505 mh.error( 

506 location=loc, 

507 message=self.message, 

508 explanation=self.extrainfo, 

509 fatal=self.severity == "fatal", 

510 user=True, 

511 ) 

512 return False 

513 

514 return True 

515 

516 

517############################################################################## 

518# AST Nodes (Expressions) 

519############################################################################## 

520 

521 

522class Unary_Operator(Enum): 

523 # lobster-exclude: Utility enumeration for unary operators 

524 MINUS = auto() 

525 PLUS = auto() 

526 LOGICAL_NOT = auto() 

527 ABSOLUTE_VALUE = auto() 

528 

529 STRING_LENGTH = auto() 

530 ARRAY_LENGTH = auto() 

531 

532 CONVERSION_TO_INT = auto() 

533 CONVERSION_TO_DECIMAL = auto() 

534 

535 

536class Binary_Operator(Enum): 

537 # lobster-exclude: Utility enumeration for binary operators 

538 LOGICAL_AND = auto() # Short-circuit 

539 LOGICAL_OR = auto() # Short-circuit 

540 LOGICAL_XOR = auto() 

541 LOGICAL_IMPLIES = auto() # Short-circuit 

542 

543 COMP_EQ = auto() 

544 COMP_NEQ = auto() 

545 COMP_LT = auto() 

546 COMP_LEQ = auto() 

547 COMP_GT = auto() 

548 COMP_GEQ = auto() 

549 

550 STRING_CONTAINS = auto() 

551 STRING_STARTSWITH = auto() 

552 STRING_ENDSWITH = auto() 

553 STRING_REGEX = auto() 

554 

555 ARRAY_CONTAINS = auto() 

556 

557 PLUS = auto() 

558 MINUS = auto() 

559 TIMES = auto() 

560 DIVIDE = auto() 

561 REMAINDER = auto() 

562 

563 POWER = auto() 

564 

565 INDEX = auto() 

566 

567 

568class Expression(Node, metaclass=ABCMeta): 

569 """Abstract base class for all expressions. 

570 

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

572 :type: Type 

573 """ 

574 

575 def __init__(self, location, typ): 

576 # lobster-exclude: Constructor only declares variables 

577 super().__init__(location) 

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

579 self.typ = typ 

580 

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

582 """Evaluate the expression in the given context 

583 

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

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

586 dictionary that maps names (such as record fields or 

587 quantified variables) to expressions. 

588 

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

590 evaluations), otherwise it must contain the global symbol 

591 table to resolve record references. 

592 

593 :param mh: the message handler to use 

594 :type mh: Message_Handler 

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

596 :type context: dict[str, Expression] 

597 :raise TRLC_Error: if the expression cannot be evaluated 

598 :return: result of the evaluation 

599 :rtype: Value 

600 

601 """ 

602 assert isinstance(mh, Message_Handler) 

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

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

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

606 

607 @abstractmethod 

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

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

610 

611 def ensure_type(self, mh, typ): 

612 # lobster-trace: LRM.Restricted_Null 

613 # lobster-trace: LRM.Null_Is_Invalid 

614 

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

616 if self.typ is None: 

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

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

619 mh.error( 

620 self.location, 

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

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

623 ) 

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

625 mh.error( 

626 self.location, 

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

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

629 ) 

630 

631 def resolve_references(self, mh): 

632 assert isinstance(mh, Message_Handler) 

633 

634 @abstractmethod 

635 def can_be_null(self): 

636 """Test if the expression could return null 

637 

638 Checks the expression if it could generate a null value 

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

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

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

642 occur earlier. 

643 

644 :return: possibility of encountering null 

645 :rtype: bool 

646 

647 """ 

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

649 

650 def uses_field_access(self): 

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

652 union reference. 

653 

654 Returns True if any sub-expression is a 

655 :class:`Field_Access_Expression` whose prefix has a 

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

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

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

659 

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

661 :rtype: bool 

662 

663 """ 

664 return False 

665 

666 

667class Implicit_Null(Expression): 

668 """Synthesised null values 

669 

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

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

672 implicit null expression for this. 

673 

674 For example given this TRLC type:: 

675 

676 type T { 

677 x optional Integer 

678 } 

679 

680 And this declaration:: 

681 

682 T Potato {} 

683 

684 Then the field mapping for Potato will be:: 

685 

686 {x: Implicit_Null} 

687 

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

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

690 that can appear in check expressions. 

691 

692 """ 

693 

694 def __init__(self, composite_object, composite_component): 

695 # lobster-trace: LRM.Unspecified_Optional_Components 

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

697 assert isinstance(composite_component, Composite_Component) 

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

699 

700 def to_string(self): 

701 return "null" 

702 

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

704 # lobster-trace: LRM.Unspecified_Optional_Components 

705 assert isinstance(mh, Message_Handler) 

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

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

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

709 

710 def to_python_object(self): 

711 return None 

712 

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

714 # lobster-exclude: Debugging feature 

715 self.write_indent(indent, "Implicit_Null") 

716 

717 def can_be_null(self): 

718 return True 

719 

720 

721class Literal(Expression, metaclass=ABCMeta): 

722 """Abstract base for all Literals 

723 

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

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

726 check if you are dealing with a literal:: 

727 

728 isinstance(my_expression, Literal) 

729 

730 """ 

731 

732 @abstractmethod 

733 def to_python_object(self): 

734 assert False 

735 

736 

737class Null_Literal(Literal): 

738 # lobster-trace: LRM.Primary 

739 """The null literal 

740 

741 This can appear in check expressions:: 

742 

743 a /= null implies a > 5 

744 ^^^^ 

745 

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

747 values that appear in record objects. 

748 

749 """ 

750 

751 def __init__(self, token): 

752 assert isinstance(token, Token) 

753 assert token.kind == "KEYWORD" 

754 assert token.value == "null" 

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

756 

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

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

759 

760 def to_string(self): 

761 return "null" 

762 

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

764 assert isinstance(mh, Message_Handler) 

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

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

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

768 

769 def to_python_object(self): 

770 return None 

771 

772 def can_be_null(self): 

773 return True 

774 

775 

776class Integer_Literal(Literal): 

777 # lobster-trace: LRM.Integer_Values 

778 # lobster-trace: LRM.Primary 

779 """Integer literals 

780 

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

782 actually a unary negation expression, operating on a positive 

783 integer literal:: 

784 

785 x == -5 

786 

787 This would create the following tree:: 

788 

789 OP_EQUALITY 

790 NAME_REFERENCE x 

791 UNARY_EXPRESSION - 

792 INTEGER_LITERAL 5 

793 

794 :attribute value: the non-negative integer value 

795 :type: int 

796 """ 

797 

798 def __init__(self, token, typ): 

799 assert isinstance(token, Token) 

800 assert token.kind == "INTEGER" 

801 assert isinstance(typ, Builtin_Integer) 

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

803 

804 self.value = token.value 

805 

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

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

808 

809 def to_string(self): 

810 return str(self.value) 

811 

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

813 assert isinstance(mh, Message_Handler) 

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

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

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

817 

818 def to_python_object(self): 

819 return self.value 

820 

821 def can_be_null(self): 

822 return False 

823 

824 

825class Decimal_Literal(Literal): 

826 # lobster-trace: LRM.Decimal_Values 

827 # lobster-trace: LRM.Primary 

828 """Decimal literals 

829 

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

831 actually a unary negation expression, operating on a positive 

832 decimal literal:: 

833 

834 x == -5.0 

835 

836 This would create the following tree:: 

837 

838 OP_EQUALITY 

839 NAME_REFERENCE x 

840 UNARY_EXPRESSION - 

841 DECIMAL_LITERAL 5.0 

842 

843 :attribute value: the non-negative decimal value 

844 :type: fractions.Fraction 

845 """ 

846 

847 def __init__(self, token, typ): 

848 assert isinstance(token, Token) 

849 assert token.kind == "DECIMAL" 

850 assert isinstance(typ, Builtin_Decimal) 

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

852 

853 self.value = token.value 

854 

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

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

857 

858 def to_string(self): 

859 return str(self.value) 

860 

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

862 assert isinstance(mh, Message_Handler) 

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

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

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

866 

867 def to_python_object(self): 

868 return float(self.value) 

869 

870 def can_be_null(self): 

871 return False 

872 

873 

874class String_Literal(Literal): 

875 # lobster-trace: LRM.String_Values 

876 # lobster-trace: LRM.Markup_String_Values 

877 # lobster-trace: LRM.Primary 

878 """String literals 

879 

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

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

882 

883 "foo\\"bar" 

884 

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

886 

887 :attribute value: string content 

888 :type: str 

889 

890 :attribute references: resolved references of a markup string 

891 :type: list[Record_Reference] 

892 

893 """ 

894 

895 def __init__(self, token, typ): 

896 assert isinstance(token, Token) 

897 assert token.kind == "STRING" 

898 assert isinstance(typ, Builtin_String) 

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

900 

901 self.value = token.value 

902 self.has_references = isinstance(typ, Builtin_Markup_String) 

903 self.references = [] 

904 

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

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

907 if self.has_references: 

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

909 for ref in self.references: 

910 ref.dump(indent + 2) 

911 

912 def to_string(self): 

913 return self.value 

914 

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

916 assert isinstance(mh, Message_Handler) 

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

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

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

920 

921 def to_python_object(self): 

922 return self.value 

923 

924 def resolve_references(self, mh): 

925 assert isinstance(mh, Message_Handler) 

926 for ref in self.references: 

927 ref.resolve_references(mh) 

928 

929 def can_be_null(self): 

930 return False 

931 

932 

933class Boolean_Literal(Literal): 

934 # lobster-trace: LRM.Boolean_Values 

935 # lobster-trace: LRM.Primary 

936 """Boolean values 

937 

938 :attribute value: the boolean value 

939 :type: bool 

940 """ 

941 

942 def __init__(self, token, typ): 

943 assert isinstance(token, Token) 

944 assert token.kind == "KEYWORD" 

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

946 assert isinstance(typ, Builtin_Boolean) 

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

948 

949 self.value = token.value == "true" 

950 

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

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

953 

954 def to_string(self): 

955 return str(self.value) 

956 

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

958 assert isinstance(mh, Message_Handler) 

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

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

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

962 

963 def to_python_object(self): 

964 return self.value 

965 

966 def can_be_null(self): 

967 return False 

968 

969 

970class Enumeration_Literal(Literal): 

971 """Enumeration values 

972 

973 Note that this is distinct from 

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

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

976 

977 foo != my_enum.POTATO 

978 ^^^^^^^^^^^^^^ 

979 

980 To get to the string value of the enumeration literal 

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

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

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

984 ``enum_lit.value.n_typ.name``. 

985 

986 :attribute value: enumeration value 

987 :type: Enumeration_Literal_Spec 

988 

989 """ 

990 

991 def __init__(self, location, literal): 

992 # lobster-exclude: Constructor only declares variables 

993 assert isinstance(literal, Enumeration_Literal_Spec) 

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

995 

996 self.value = literal 

997 

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

999 # lobster-exclude: Debugging feature 

1000 self.write_indent( 

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

1002 ) 

1003 

1004 def to_string(self): 

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

1006 

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

1008 assert isinstance(mh, Message_Handler) 

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

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

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

1012 

1013 def to_python_object(self): 

1014 return self.value.name 

1015 

1016 def can_be_null(self): 

1017 return False 

1018 

1019 

1020class Array_Aggregate(Expression): 

1021 """Instances of array types 

1022 

1023 This is created when assigning to array components:: 

1024 

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

1026 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 

1027 

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

1029 limited: 

1030 

1031 * :class:`Literal` 

1032 * :class:`Array_Aggregate` 

1033 * :class:`Record_Reference` 

1034 

1035 :attribute value: contents of the array 

1036 :type: list[Expression] 

1037 

1038 """ 

1039 

1040 def __init__(self, location, typ): 

1041 # lobster-trace: LRM.Record_Object_Declaration 

1042 

1043 super().__init__(location, typ) 

1044 self.value = [] 

1045 

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

1047 # lobster-exclude: Debugging feature 

1048 self.write_indent(indent, "Array_Aggregate") 

1049 for n_value in self.value: 

1050 n_value.dump(indent + 1) 

1051 

1052 def append(self, value): 

1053 assert isinstance( 

1054 value, 

1055 ( 

1056 Literal, 

1057 Unary_Expression, 

1058 Array_Aggregate, 

1059 Tuple_Aggregate, 

1060 Record_Reference, 

1061 ), 

1062 ) 

1063 self.value.append(value) 

1064 

1065 def to_string(self): 

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

1067 

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

1069 assert isinstance(mh, Message_Handler) 

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

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

1072 return Value( 

1073 self.location, 

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

1075 self.typ, 

1076 ) 

1077 

1078 def resolve_references(self, mh): 

1079 assert isinstance(mh, Message_Handler) 

1080 

1081 for val in self.value: 

1082 val.resolve_references(mh) 

1083 

1084 def to_python_object(self): 

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

1086 

1087 def can_be_null(self): 

1088 return False 

1089 

1090 def uses_field_access(self): 

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

1092 

1093 

1094class Tuple_Aggregate(Expression): 

1095 """Instances of a tuple 

1096 

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

1098 two forms, the ordinary form:: 

1099 

1100 coordinate = (12.3, 40.0) 

1101 ^^^^^^^^^^^^ 

1102 

1103 And the separator form:: 

1104 

1105 item = 12345@42 

1106 ^^^^^^^^ 

1107 

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

1109 syntactic sugar. 

1110 

1111 :attribute value: contents of the tuple 

1112 :type: dict[str, Expression] 

1113 

1114 """ 

1115 

1116 def __init__(self, location, typ): 

1117 # lobster-trace: LRM.Unspecified_Optional_Components 

1118 # lobster-trace: LRM.Record_Object_Declaration 

1119 

1120 super().__init__(location, typ) 

1121 self.value = { 

1122 n_field.name: Implicit_Null(self, n_field) 

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

1124 } 

1125 

1126 def assign(self, field, value): 

1127 assert isinstance(field, str) 

1128 assert isinstance( 

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

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

1131 assert field in self.typ.components 

1132 

1133 self.value[field] = value 

1134 

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

1136 # lobster-exclude: Debugging feature 

1137 self.write_indent(indent, "Tuple_Aggregate") 

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

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

1140 if isinstance(n_item, Composite_Component): 

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

1142 

1143 def to_string(self): 

1144 first = True 

1145 if self.typ.has_separators(): 

1146 rv = "" 

1147 else: 

1148 rv = "(" 

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

1150 if isinstance(n_item, Separator): 

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

1152 elif first: 

1153 first = False 

1154 else: 

1155 rv += ", " 

1156 

1157 if isinstance(n_item, Composite_Component): 

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

1159 if self.typ.has_separators(): 

1160 rv = "" 

1161 else: 

1162 rv = ")" 

1163 return rv 

1164 

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

1166 assert isinstance(mh, Message_Handler) 

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

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

1169 return Value( 

1170 self.location, 

1171 { 

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

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

1174 }, 

1175 self.typ, 

1176 ) 

1177 

1178 def resolve_references(self, mh): 

1179 assert isinstance(mh, Message_Handler) 

1180 

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

1182 val.resolve_references(mh) 

1183 

1184 def to_python_object(self): 

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

1186 

1187 def can_be_null(self): 

1188 return False 

1189 

1190 def uses_field_access(self): 

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

1192 

1193 

1194class Record_Reference(Expression): 

1195 """Reference to another record object 

1196 

1197 This can appear in record object declarations:: 

1198 

1199 Requirement Kitten { 

1200 depends_on = Other_Package.Cat 

1201 ^1 ^2 

1202 } 

1203 

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

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

1206 the target attribute. 

1207 

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

1209 immediately resolved on parsing in the TRLC language. 

1210 

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

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

1213 in this AST node. 

1214 

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

1216 :type: str 

1217 

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

1219 :type: Record_Object 

1220 

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

1222 :type: Package 

1223 

1224 """ 

1225 

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

1227 # lobster-exclude: Constructor only declares variables 

1228 assert isinstance(location, Location) 

1229 assert isinstance(name, str) 

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

1231 assert isinstance(package, Package) 

1232 super().__init__(location, typ) 

1233 

1234 self.name = name 

1235 self.target = None 

1236 self.package = package 

1237 

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

1239 # lobster-exclude: Debugging feature 

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

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

1242 

1243 def to_string(self): 

1244 return self.name 

1245 

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

1247 assert isinstance(mh, Message_Handler) 

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

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

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

1251 

1252 def resolve_references(self, mh): 

1253 # lobster-trace: LRM.References_To_Extensions 

1254 # lobster-trace: LRM.Union_Type_Minimum_Members 

1255 assert isinstance(mh, Message_Handler) 

1256 

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

1258 mh=mh, 

1259 name=self.name, 

1260 error_location=self.location, 

1261 required_subclass=Record_Object, 

1262 ) 

1263 if self.typ is None: 

1264 self.typ = self.target.n_typ 

1265 elif isinstance(self.typ, Union_Type): 

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

1267 mh.error( 

1268 self.location, 

1269 "expected reference of type %s," 

1270 " but %s is of type %s" 

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

1272 ) 

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

1274 mh.error( 

1275 self.location, 

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

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

1278 ) 

1279 

1280 def to_python_object(self): 

1281 return self.target.fully_qualified_name() 

1282 

1283 def can_be_null(self): 

1284 return False 

1285 

1286 

1287class Name_Reference(Expression): 

1288 # lobster-trace: LRM.Qualified_Name 

1289 # lobster-trace: LRM.Static_Regular_Expression 

1290 

1291 """Reference to a name 

1292 

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

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

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

1296 

1297 For example:: 

1298 

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

1300 ^1 ^2 

1301 

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

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

1304 :class:`Quantified_Variable`. 

1305 

1306 :attribute entity: the entity named here 

1307 :type: Composite_Component, Quantified_Variable 

1308 """ 

1309 

1310 def __init__(self, location, entity): 

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

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

1313 self.entity = entity 

1314 

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

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

1317 

1318 def to_string(self): 

1319 return self.entity.name 

1320 

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

1322 assert isinstance(mh, Message_Handler) 

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

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

1325 

1326 if context is None: 

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

1328 

1329 assert self.entity.name in context 

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

1331 

1332 def can_be_null(self): 

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

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

1335 # optional. 

1336 if isinstance(self.entity, Composite_Component): 

1337 return self.entity.optional 

1338 else: 

1339 return False 

1340 

1341 

1342class Unary_Expression(Expression): 

1343 """Expression with only one operand 

1344 

1345 This captures the following operations: 

1346 

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

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

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

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

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

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

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

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

1355 

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

1357 

1358 :attribute operator: the operation 

1359 :type: Unary_Operator 

1360 

1361 :attribute n_operand: the expression we operate on 

1362 :type: Expression 

1363 

1364 """ 

1365 

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

1367 # lobster-trace: LRM.Simple_Expression 

1368 # lobster-trace: LRM.Relation 

1369 # lobster-trace: LRM.Factor 

1370 # lobster-trace: LRM.Signature_Len 

1371 # lobster-trace: LRM.Signature_Type_Conversion 

1372 

1373 super().__init__(location, typ) 

1374 assert isinstance(mh, Message_Handler) 

1375 assert isinstance(operator, Unary_Operator) 

1376 assert isinstance(n_operand, Expression) 

1377 self.operator = operator 

1378 self.n_operand = n_operand 

1379 

1380 if operator in ( 

1381 Unary_Operator.MINUS, 

1382 Unary_Operator.PLUS, 

1383 Unary_Operator.ABSOLUTE_VALUE, 

1384 ): 

1385 self.n_operand.ensure_type(mh, Builtin_Numeric_Type) 

1386 elif operator == Unary_Operator.LOGICAL_NOT: 

1387 self.n_operand.ensure_type(mh, Builtin_Boolean) 

1388 elif operator == Unary_Operator.STRING_LENGTH: 

1389 self.n_operand.ensure_type(mh, Builtin_String) 

1390 elif operator == Unary_Operator.ARRAY_LENGTH: 

1391 self.n_operand.ensure_type(mh, Array_Type) 

1392 elif operator == Unary_Operator.CONVERSION_TO_INT: 

1393 self.n_operand.ensure_type(mh, Builtin_Numeric_Type) 

1394 elif operator == Unary_Operator.CONVERSION_TO_DECIMAL: 

1395 self.n_operand.ensure_type(mh, Builtin_Numeric_Type) 

1396 else: 

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

1398 

1399 def to_string(self): 

1400 prefix_operators = { 

1401 Unary_Operator.MINUS: "-", 

1402 Unary_Operator.PLUS: "+", 

1403 Unary_Operator.ABSOLUTE_VALUE: "abs ", 

1404 Unary_Operator.LOGICAL_NOT: "not ", 

1405 } 

1406 function_calls = { 

1407 Unary_Operator.STRING_LENGTH: "len", 

1408 Unary_Operator.ARRAY_LENGTH: "len", 

1409 Unary_Operator.CONVERSION_TO_INT: "Integer", 

1410 Unary_Operator.CONVERSION_TO_DECIMAL: "Decimal", 

1411 } 

1412 

1413 if self.operator in prefix_operators: 

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

1415 

1416 elif self.operator in function_calls: 

1417 return "%s(%s)" % ( 

1418 function_calls[self.operator], 

1419 self.n_operand.to_string(), 

1420 ) 

1421 

1422 else: 

1423 assert False 

1424 

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

1426 # lobster-exclude: Debugging feature 

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

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

1429 self.n_operand.dump(indent + 1) 

1430 

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

1432 # lobster-trace: LRM.Null_Is_Invalid 

1433 # lobster-trace: LRM.Signature_Len 

1434 # lobster-trace: LRM.Signature_Type_Conversion 

1435 # lobster-trace: LRM.Len_Semantics 

1436 # lobster-trace: LRM.Integer_Conversion_Semantics 

1437 # lobster-trace: LRM.Decimal_Conversion_Semantics 

1438 

1439 assert isinstance(mh, Message_Handler) 

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

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

1442 

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

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

1445 mh.error( 

1446 v_operand.location, 

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

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

1449 ) 

1450 

1451 if self.operator == Unary_Operator.MINUS: 

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

1453 elif self.operator == Unary_Operator.PLUS: 

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

1455 elif self.operator == Unary_Operator.LOGICAL_NOT: 

1456 return Value( 

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

1458 ) 

1459 elif self.operator == Unary_Operator.ABSOLUTE_VALUE: 

1460 return Value( 

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

1462 ) 

1463 elif self.operator in ( 

1464 Unary_Operator.STRING_LENGTH, 

1465 Unary_Operator.ARRAY_LENGTH, 

1466 ): 

1467 return Value( 

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

1469 ) 

1470 elif self.operator == Unary_Operator.CONVERSION_TO_INT: 

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

1472 return Value( 

1473 location=self.location, 

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

1475 typ=self.typ, 

1476 ) 

1477 else: 

1478 return Value( 

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

1480 ) 

1481 elif self.operator == Unary_Operator.CONVERSION_TO_DECIMAL: 

1482 return Value( 

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

1484 ) 

1485 else: 

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

1487 

1488 def to_python_object(self): 

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

1490 val = self.n_operand.to_python_object() 

1491 if self.operator == Unary_Operator.MINUS: 

1492 return -val 

1493 else: 

1494 return val 

1495 

1496 def can_be_null(self): 

1497 return False 

1498 

1499 def uses_field_access(self): 

1500 return self.n_operand.uses_field_access() 

1501 

1502 

1503class Binary_Expression(Expression): 

1504 """Expression with two operands 

1505 

1506 This captures the following operations: 

1507 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1530 

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

1532 

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

1534 rationals and strings. 

1535 

1536 :attribute operator: the operation 

1537 :type: Binary_Operator 

1538 

1539 :attribute n_lhs: the first operand 

1540 :type: Expression 

1541 

1542 :attribute n_rhs: the second operand 

1543 :type: Expression 

1544 

1545 """ 

1546 

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

1548 # lobster-trace: LRM.Expression 

1549 # lobster-trace: LRM.Relation 

1550 # lobster-trace: LRM.Simple_Expression 

1551 # lobster-trace: LRM.Term 

1552 # lobster-trace: LRM.Factor 

1553 # lobster-trace: LRM.Signature_String_End_Functions 

1554 # lobster-trace: LRM.Signature_Matches 

1555 

1556 super().__init__(location, typ) 

1557 assert isinstance(mh, Message_Handler) 

1558 assert isinstance(operator, Binary_Operator) 

1559 assert isinstance(n_lhs, Expression) 

1560 assert isinstance(n_rhs, Expression) 

1561 self.operator = operator 

1562 self.n_lhs = n_lhs 

1563 self.n_rhs = n_rhs 

1564 

1565 if operator in ( 

1566 Binary_Operator.LOGICAL_AND, 

1567 Binary_Operator.LOGICAL_OR, 

1568 Binary_Operator.LOGICAL_XOR, 

1569 Binary_Operator.LOGICAL_IMPLIES, 

1570 ): 

1571 self.n_lhs.ensure_type(mh, Builtin_Boolean) 

1572 self.n_rhs.ensure_type(mh, Builtin_Boolean) 

1573 

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

1575 # lobster-trace: LRM.Union_Type_Equality 

1576 # lobster-trace: LRM.Union_Type_Equality_Domain 

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

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

1579 pass 

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

1581 self.n_rhs.typ, Union_Type 

1582 ): 

1583 # For union types, we allow comparison if both 

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

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

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

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

1588 mh.error( 

1589 self.location, 

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

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

1592 ) 

1593 else: 

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

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

1596 # of the other. This implements Equality_Domain for 

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

1598 lhs_members = ( 

1599 self.n_lhs.typ.types 

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

1601 else [self.n_lhs.typ] 

1602 ) 

1603 rhs_members = ( 

1604 self.n_rhs.typ.types 

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

1606 else [self.n_rhs.typ] 

1607 ) 

1608 if not any( 

1609 lm.is_subclass_of(rm) or rm.is_subclass_of(lm) 

1610 for lm in lhs_members 

1611 for rm in rhs_members 

1612 ): 

1613 mh.error( 

1614 self.location, 

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

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

1617 ) 

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

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

1620 # types match 

1621 mh.error( 

1622 self.location, 

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

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

1625 ) 

1626 

1627 elif operator in ( 

1628 Binary_Operator.COMP_LT, 

1629 Binary_Operator.COMP_LEQ, 

1630 Binary_Operator.COMP_GT, 

1631 Binary_Operator.COMP_GEQ, 

1632 ): 

1633 self.n_lhs.ensure_type(mh, Builtin_Numeric_Type) 

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

1635 

1636 elif operator in ( 

1637 Binary_Operator.STRING_CONTAINS, 

1638 Binary_Operator.STRING_STARTSWITH, 

1639 Binary_Operator.STRING_ENDSWITH, 

1640 Binary_Operator.STRING_REGEX, 

1641 ): 

1642 self.n_lhs.ensure_type(mh, Builtin_String) 

1643 self.n_rhs.ensure_type(mh, Builtin_String) 

1644 

1645 elif operator == Binary_Operator.ARRAY_CONTAINS: 

1646 self.n_rhs.ensure_type(mh, Array_Type) 

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

1648 

1649 elif operator == Binary_Operator.PLUS: 

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

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

1652 else: 

1653 self.n_lhs.ensure_type(mh, Builtin_String) 

1654 self.n_rhs.ensure_type(mh, Builtin_String) 

1655 

1656 elif operator in ( 

1657 Binary_Operator.MINUS, 

1658 Binary_Operator.TIMES, 

1659 Binary_Operator.DIVIDE, 

1660 ): 

1661 self.n_lhs.ensure_type(mh, Builtin_Numeric_Type) 

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

1663 

1664 elif operator == Binary_Operator.POWER: 

1665 self.n_lhs.ensure_type(mh, Builtin_Numeric_Type) 

1666 self.n_rhs.ensure_type(mh, Builtin_Integer) 

1667 

1668 elif operator == Binary_Operator.REMAINDER: 

1669 self.n_lhs.ensure_type(mh, Builtin_Integer) 

1670 self.n_rhs.ensure_type(mh, Builtin_Integer) 

1671 

1672 elif operator == Binary_Operator.INDEX: 

1673 self.n_lhs.ensure_type(mh, Array_Type) 

1674 self.n_rhs.ensure_type(mh, Builtin_Integer) 

1675 

1676 else: 

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

1678 

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

1680 # lobster-exclude: Debugging feature 

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

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

1683 self.n_lhs.dump(indent + 1) 

1684 self.n_rhs.dump(indent + 1) 

1685 

1686 def to_string(self): 

1687 infix_operators = { 

1688 Binary_Operator.LOGICAL_AND: "and", 

1689 Binary_Operator.LOGICAL_OR: "or", 

1690 Binary_Operator.LOGICAL_XOR: "xor", 

1691 Binary_Operator.LOGICAL_IMPLIES: "implies", 

1692 Binary_Operator.COMP_EQ: "==", 

1693 Binary_Operator.COMP_NEQ: "!=", 

1694 Binary_Operator.COMP_LT: "<", 

1695 Binary_Operator.COMP_LEQ: "<=", 

1696 Binary_Operator.COMP_GT: ">", 

1697 Binary_Operator.COMP_GEQ: ">=", 

1698 Binary_Operator.STRING_CONTAINS: "in", 

1699 Binary_Operator.ARRAY_CONTAINS: "in", 

1700 Binary_Operator.PLUS: "+", 

1701 Binary_Operator.MINUS: "-", 

1702 Binary_Operator.TIMES: "*", 

1703 Binary_Operator.DIVIDE: "/", 

1704 Binary_Operator.REMAINDER: "%", 

1705 Binary_Operator.POWER: "**", 

1706 } 

1707 string_functions = { 

1708 Binary_Operator.STRING_STARTSWITH: "startswith", 

1709 Binary_Operator.STRING_ENDSWITH: "endswith", 

1710 Binary_Operator.STRING_REGEX: "matches", 

1711 } 

1712 

1713 if self.operator in infix_operators: 

1714 return "%s %s %s" % ( 

1715 self.n_lhs.to_string(), 

1716 infix_operators[self.operator], 

1717 self.n_rhs.to_string(), 

1718 ) 

1719 

1720 elif self.operator in string_functions: 

1721 return "%s(%s, %s)" % ( 

1722 string_functions[self.operator], 

1723 self.n_lhs.to_string(), 

1724 self.n_rhs.to_string(), 

1725 ) 

1726 

1727 elif self.operator == Binary_Operator.INDEX: 

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

1729 

1730 else: 

1731 assert False 

1732 

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

1734 # lobster-trace: LRM.Null_Equivalence 

1735 # lobster-trace: LRM.Null_Is_Invalid 

1736 # lobster-trace: LRM.Signature_String_End_Functions 

1737 # lobster-trace: LRM.Signature_Matches 

1738 # lobster-trace: LRM.Startswith_Semantics 

1739 # lobster-trace: LRM.Endswith_Semantics 

1740 # lobster-trace: LRM.Matches_Semantics 

1741 

1742 assert isinstance(mh, Message_Handler) 

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

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

1745 

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

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

1748 Binary_Operator.COMP_EQ, 

1749 Binary_Operator.COMP_NEQ, 

1750 ): 

1751 mh.error( 

1752 v_lhs.location, 

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

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

1755 ) 

1756 

1757 # Check for the short-circuit operators first 

1758 if self.operator == Binary_Operator.LOGICAL_AND: 

1759 assert isinstance(v_lhs.value, bool) 

1760 if v_lhs.value: 

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

1762 else: 

1763 return v_lhs 

1764 

1765 elif self.operator == Binary_Operator.LOGICAL_OR: 

1766 assert isinstance(v_lhs.value, bool) 

1767 if v_lhs.value: 

1768 return v_lhs 

1769 else: 

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

1771 

1772 elif self.operator == Binary_Operator.LOGICAL_IMPLIES: 

1773 assert isinstance(v_lhs.value, bool) 

1774 if v_lhs.value: 

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

1776 else: 

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

1778 

1779 # Otherwise, evaluate RHS and do the operation 

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

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

1782 Binary_Operator.COMP_EQ, 

1783 Binary_Operator.COMP_NEQ, 

1784 ): 

1785 mh.error( 

1786 v_rhs.location, 

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

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

1789 ) 

1790 

1791 if self.operator == Binary_Operator.LOGICAL_XOR: 

1792 assert isinstance(v_lhs.value, bool) 

1793 assert isinstance(v_rhs.value, bool) 

1794 return Value( 

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

1796 ) 

1797 

1798 elif self.operator == Binary_Operator.COMP_EQ: 

1799 return Value( 

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

1801 ) 

1802 

1803 elif self.operator == Binary_Operator.COMP_NEQ: 

1804 return Value( 

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

1806 ) 

1807 

1808 elif self.operator in ( 

1809 Binary_Operator.COMP_LT, 

1810 Binary_Operator.COMP_LEQ, 

1811 Binary_Operator.COMP_GT, 

1812 Binary_Operator.COMP_GEQ, 

1813 ): 

1814 return Value( 

1815 location=self.location, 

1816 value={ 

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

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

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

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

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

1822 typ=self.typ, 

1823 ) 

1824 

1825 elif self.operator == Binary_Operator.STRING_CONTAINS: 

1826 assert isinstance(v_lhs.value, str) 

1827 assert isinstance(v_rhs.value, str) 

1828 

1829 return Value( 

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

1831 ) 

1832 

1833 elif self.operator == Binary_Operator.STRING_STARTSWITH: 

1834 assert isinstance(v_lhs.value, str) 

1835 assert isinstance(v_rhs.value, str) 

1836 return Value( 

1837 location=self.location, 

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

1839 typ=self.typ, 

1840 ) 

1841 

1842 elif self.operator == Binary_Operator.STRING_ENDSWITH: 

1843 assert isinstance(v_lhs.value, str) 

1844 assert isinstance(v_rhs.value, str) 

1845 return Value( 

1846 location=self.location, 

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

1848 typ=self.typ, 

1849 ) 

1850 

1851 elif self.operator == Binary_Operator.STRING_REGEX: 

1852 assert isinstance(v_lhs.value, str) 

1853 assert isinstance(v_rhs.value, str) 

1854 return Value( 

1855 location=self.location, 

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

1857 typ=self.typ, 

1858 ) 

1859 

1860 elif self.operator == Binary_Operator.ARRAY_CONTAINS: 

1861 assert isinstance(v_rhs.value, list) 

1862 

1863 return Value( 

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

1865 ) 

1866 

1867 elif self.operator == Binary_Operator.PLUS: 

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

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

1870 return Value( 

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

1872 ) 

1873 

1874 elif self.operator == Binary_Operator.MINUS: 

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

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

1877 return Value( 

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

1879 ) 

1880 

1881 elif self.operator == Binary_Operator.TIMES: 

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

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

1884 return Value( 

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

1886 ) 

1887 

1888 elif self.operator == Binary_Operator.DIVIDE: 

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

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

1891 

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

1893 mh.error( 

1894 v_rhs.location, 

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

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

1897 ) 

1898 

1899 if isinstance(v_lhs.value, int): 

1900 return Value( 

1901 location=self.location, 

1902 value=v_lhs.value // v_rhs.value, 

1903 typ=self.typ, 

1904 ) 

1905 else: 

1906 return Value( 

1907 location=self.location, 

1908 value=v_lhs.value / v_rhs.value, 

1909 typ=self.typ, 

1910 ) 

1911 

1912 elif self.operator == Binary_Operator.REMAINDER: 

1913 assert isinstance(v_lhs.value, int) 

1914 assert isinstance(v_rhs.value, int) 

1915 

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

1917 mh.error( 

1918 v_rhs.location, 

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

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

1921 ) 

1922 

1923 return Value( 

1924 location=self.location, 

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

1926 typ=self.typ, 

1927 ) 

1928 

1929 elif self.operator == Binary_Operator.POWER: 

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

1931 assert isinstance(v_rhs.value, int) 

1932 return Value( 

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

1934 ) 

1935 

1936 elif self.operator == Binary_Operator.INDEX: 

1937 assert isinstance(v_lhs.value, list) 

1938 assert isinstance(v_rhs.value, int) 

1939 

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

1941 mh.error( 

1942 v_rhs.location, 

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

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

1945 ) 

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

1947 v_lhs.typ.upper_bound is not None 

1948 and v_rhs.value > v_lhs.typ.upper_bound 

1949 ): 

1950 mh.error( 

1951 v_rhs.location, 

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

1953 % ( 

1954 v_lhs.typ.upper_bound, 

1955 self.to_string(), 

1956 mh.cross_file_reference(self.location), 

1957 ), 

1958 ) 

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

1960 mh.error( 

1961 v_lhs.location, 

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

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

1964 ) 

1965 

1966 return Value( 

1967 location=self.location, 

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

1969 typ=self.typ, 

1970 ) 

1971 

1972 else: 

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

1974 

1975 def can_be_null(self): 

1976 return False 

1977 

1978 def uses_field_access(self): 

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

1980 

1981 

1982class Field_Access_Expression(Expression): 

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

1984 

1985 For example in:: 

1986 

1987 foo.bar 

1988 ^1 ^2 

1989 

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

1991 :type: Expression 

1992 

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

1994 :type: Composite_Component 

1995 

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

1997 :type: bool 

1998 

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

2000 Only meaningful when is_union_access is True. 

2001 :type: bool 

2002 

2003 """ 

2004 

2005 def __init__( 

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

2007 ): 

2008 # lobster-trace: LRM.Union_Type_Field_Access 

2009 assert isinstance(mh, Message_Handler) 

2010 assert isinstance(n_prefix, Expression) 

2011 assert isinstance(n_field, Composite_Component) 

2012 assert isinstance(is_union_access, bool) 

2013 assert isinstance(is_universal, bool) 

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

2015 self.n_prefix = n_prefix 

2016 self.n_field = n_field 

2017 self.is_union_access = is_union_access 

2018 self.is_universal = is_universal 

2019 

2020 if not is_union_access: 

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

2022 

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

2024 # lobster-exclude: Debugging feature 

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

2026 self.n_prefix.dump(indent + 1) 

2027 

2028 def to_string(self): 

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

2030 

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

2032 assert isinstance(mh, Message_Handler) 

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

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

2035 

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

2037 if v_prefix is None: 

2038 # lobster-trace: LRM.Dereference 

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

2040 

2041 # lobster-trace: LRM.Union_Type_Partial_Field_Access 

2042 # lobster-trace: LRM.Union_Type_Partial_Field_Null 

2043 if self.n_field.name not in v_prefix: 

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

2045 

2046 v_field = v_prefix[self.n_field.name] 

2047 if isinstance(v_field, Expression): 

2048 # lobster-trace: LRM.Dereference 

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

2050 else: 

2051 return v_field 

2052 

2053 def can_be_null(self): 

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

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

2056 # report True in that case. 

2057 return self.is_union_access and not self.is_universal 

2058 

2059 def uses_field_access(self): 

2060 # lobster-trace: LRM.Dereference 

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

2062 return True 

2063 return self.n_prefix.uses_field_access() 

2064 

2065 

2066class Range_Test(Expression): 

2067 """Range membership test 

2068 

2069 For example in:: 

2070 

2071 x in 1 .. field+1 

2072 ^lhs ^lower ^^^^^^^upper 

2073 

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

2075 you can have arbitrarily complex expressions here. 

2076 

2077 :attribute n_lhs: the expression to test 

2078 :type: Expression 

2079 

2080 :attribute n_lower: the lower bound 

2081 :type: Expression 

2082 

2083 :attribute n_upper: the upper bound 

2084 :type: Expression 

2085 

2086 """ 

2087 

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

2089 # lobster-trace: LRM.Relation 

2090 super().__init__(location, typ) 

2091 assert isinstance(mh, Message_Handler) 

2092 assert isinstance(n_lhs, Expression) 

2093 assert isinstance(n_lower, Expression) 

2094 assert isinstance(n_upper, Expression) 

2095 self.n_lhs = n_lhs 

2096 self.n_lower = n_lower 

2097 self.n_upper = n_upper 

2098 

2099 self.n_lhs.ensure_type(mh, Builtin_Numeric_Type) 

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

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

2102 

2103 def to_string(self): 

2104 return "%s in %s .. %s" % ( 

2105 self.n_lhs.to_string(), 

2106 self.n_lower.to_string(), 

2107 self.n_upper.to_string(), 

2108 ) 

2109 

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

2111 # lobster-exclude: Debugging feature 

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

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

2114 self.n_lhs.dump(indent + 1) 

2115 self.n_lower.dump(indent + 1) 

2116 self.n_upper.dump(indent + 1) 

2117 

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

2119 # lobster-trace: LRM.Null_Is_Invalid 

2120 assert isinstance(mh, Message_Handler) 

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

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

2123 

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

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

2126 mh.error( 

2127 v_lhs.location, 

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

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

2130 ) 

2131 

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

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

2134 mh.error( 

2135 v_lower.location, 

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

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

2138 ) 

2139 

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

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

2142 mh.error( 

2143 v_upper.location, 

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

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

2146 ) 

2147 

2148 return Value( 

2149 location=self.location, 

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

2151 typ=self.typ, 

2152 ) 

2153 

2154 def can_be_null(self): 

2155 return False 

2156 

2157 def uses_field_access(self): 

2158 return ( 

2159 self.n_lhs.uses_field_access() 

2160 or self.n_lower.uses_field_access() 

2161 or self.n_upper.uses_field_access() 

2162 ) 

2163 

2164 

2165class OneOf_Expression(Expression): 

2166 """OneOf expression 

2167 

2168 For example in:: 

2169 

2170 oneof(a, b, c) 

2171 ^^^^^^^ choices 

2172 

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

2174 :type: list[Expression] 

2175 """ 

2176 

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

2178 # lobster-trace: LRM.Signature_OneOf 

2179 super().__init__(location, typ) 

2180 assert isinstance(typ, Builtin_Boolean) 

2181 assert isinstance(mh, Message_Handler) 

2182 assert isinstance(choices, list) 

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

2184 self.choices = choices 

2185 

2186 for n_choice in choices: 

2187 n_choice.ensure_type(mh, Builtin_Boolean) 

2188 

2189 def to_string(self): 

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

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

2192 ) 

2193 

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

2195 # lobster-exclude: Debugging feature 

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

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

2198 for n_choice in self.choices: 

2199 n_choice.dump(indent + 1) 

2200 

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

2202 # lobster-trace: LRM.OneOf_Semantics 

2203 assert isinstance(mh, Message_Handler) 

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

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

2206 

2207 v_choices = [ 

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

2209 ] 

2210 

2211 return Value( 

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

2213 ) 

2214 

2215 def can_be_null(self): 

2216 return False 

2217 

2218 def uses_field_access(self): 

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

2220 

2221 

2222class Action(Node): 

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

2224 

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

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

2227 Actions:: 

2228 

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

2230 ^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^ 

2231 

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

2233 the :class:`Conditional_Expression` itself. 

2234 

2235 :attribute kind: Either if or elseif 

2236 :type: str 

2237 

2238 :attribute n_cond: The boolean condition expression 

2239 :type: Expression 

2240 

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

2242 :type: Expression 

2243 

2244 """ 

2245 

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

2247 # lobster-trace: LRM.Conditional_Expression 

2248 assert isinstance(mh, Message_Handler) 

2249 assert isinstance(t_kind, Token) 

2250 assert t_kind.kind == "KEYWORD" 

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

2252 assert isinstance(n_condition, Expression) 

2253 assert isinstance(n_expression, Expression) 

2254 super().__init__(t_kind.location) 

2255 self.kind = t_kind.value 

2256 self.n_cond = n_condition 

2257 self.n_expr = n_expression 

2258 # lobster-trace: LRM.Conditional_Expression_Types 

2259 self.n_cond.ensure_type(mh, Builtin_Boolean) 

2260 

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

2262 # lobster-exclude: Debugging feature 

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

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

2265 self.n_cond.dump(indent + 2) 

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

2267 self.n_expr.dump(indent + 2) 

2268 

2269 def to_string(self): 

2270 return "%s %s then %s" % ( 

2271 self.kind, 

2272 self.n_cond.to_string(), 

2273 self.n_expr.to_string(), 

2274 ) 

2275 

2276 

2277class Conditional_Expression(Expression): 

2278 """A conditional expression 

2279 

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

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

2282 expression with two Actions:: 

2283 

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

2285 ^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^ 

2286 

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

2288 

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

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

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

2292 of all actions and the else expression will match. 

2293 

2294 :attribute actions: a list of Actions 

2295 :type: list[Action] 

2296 

2297 :attribute else_expr: the else expression 

2298 :type: Expression 

2299 

2300 """ 

2301 

2302 def __init__(self, location, if_action): 

2303 # lobster-trace: LRM.Conditional_Expression 

2304 assert isinstance(if_action, Action) 

2305 assert if_action.kind == "if" 

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

2307 self.actions = [if_action] 

2308 self.else_expr = None 

2309 

2310 def add_elsif(self, mh, n_action): 

2311 # lobster-trace: LRM.Conditional_Expression 

2312 # lobster-trace; LRM.Conditional_Expression_Types 

2313 assert isinstance(mh, Message_Handler) 

2314 assert isinstance(n_action, Action) 

2315 assert n_action.kind == "elsif" 

2316 

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

2318 self.actions.append(n_action) 

2319 

2320 def set_else_part(self, mh, n_expr): 

2321 # lobster-trace: LRM.Conditional_Expression 

2322 # lobster-trace; LRM.Conditional_Expression_Types 

2323 assert isinstance(mh, Message_Handler) 

2324 assert isinstance(n_expr, Expression) 

2325 

2326 n_expr.ensure_type(mh, self.typ) 

2327 self.else_expr = n_expr 

2328 

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

2330 # lobster-exclude: Debugging feature 

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

2332 for action in self.actions: 

2333 action.dump(indent + 1) 

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

2335 self.else_expr.dump(indent + 2) 

2336 

2337 def to_string(self): 

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

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

2340 rv += ")" 

2341 return rv 

2342 

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

2344 # lobster-trace: LRM.Conditional_Expression_Else 

2345 # lobster-trace: LRM.Conditional_Expression_Evaluation 

2346 # lobster-trace: LRM.Null_Is_Invalid 

2347 assert isinstance(mh, Message_Handler) 

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

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

2350 

2351 for action in self.actions: 

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

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

2354 mh.error( 

2355 v_cond.location, 

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

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

2358 ) 

2359 if v_cond.value: 

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

2361 

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

2363 

2364 def can_be_null(self): 

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

2366 return True 

2367 

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

2369 

2370 def uses_field_access(self): 

2371 return any( 

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

2373 for action in self.actions 

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

2375 

2376 

2377class Quantified_Expression(Expression): 

2378 """A quantified expression 

2379 

2380 For example:: 

2381 

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

2383 ^4 ^1 ^2 ^^^^^3 

2384 

2385 A quantified expression introduces and binds a 

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

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

2388 each component of the source in turn. 

2389 

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

2391 :type: Quantified_Variable 

2392 

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

2394 :type: Name_Reference 

2395 

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

2397 :type: Expression 

2398 

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

2400 :type: Boolean 

2401 

2402 """ 

2403 

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

2405 # lobster-trace: LRM.Quantified_Expression 

2406 # lobster-trace: LRM.Quantification_Type 

2407 super().__init__(location, typ) 

2408 assert isinstance(typ, Builtin_Boolean) 

2409 assert isinstance(universal, bool) 

2410 assert isinstance(n_variable, Quantified_Variable) 

2411 assert isinstance(n_expr, Expression) 

2412 assert isinstance(n_source, Name_Reference) 

2413 self.universal = universal 

2414 self.n_var = n_variable 

2415 self.n_expr = n_expr 

2416 self.n_source = n_source 

2417 self.n_expr.ensure_type(mh, Builtin_Boolean) 

2418 

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

2420 # lobster-exclude: Debugging feature 

2421 if self.universal: 

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

2423 else: 

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

2425 self.n_var.dump(indent + 1) 

2426 self.n_expr.dump(indent + 1) 

2427 

2428 def to_string(self): 

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

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

2431 self.n_var.name, 

2432 self.n_source.to_string(), 

2433 self.n_expr.to_string(), 

2434 ) 

2435 

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

2437 # lobster-trace: LRM.Null_Is_Invalid 

2438 # lobster-trace: LRM.Universal_Quantification_Semantics 

2439 # lobster-trace: LRM.Existential_Quantification_Semantics 

2440 assert isinstance(mh, Message_Handler) 

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

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

2443 

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

2445 new_ctx = {} 

2446 else: 

2447 new_ctx = copy(context) 

2448 

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

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

2451 # error messages. 

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

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

2454 if isinstance(array_values, Implicit_Null): 

2455 mh.error( 

2456 array_values.location, 

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

2458 "must not be null" 

2459 % ( 

2460 self.n_source.to_string(), 

2461 self.to_string(), 

2462 mh.cross_file_reference(self.location), 

2463 ), 

2464 ) 

2465 else: 

2466 assert isinstance(array_values, Array_Aggregate) 

2467 

2468 rv = self.universal 

2469 loc = self.location 

2470 for binding in array_values.value: 

2471 new_ctx[self.n_var.name] = binding 

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

2473 assert isinstance(result.value, bool) 

2474 if self.universal and not result.value: 

2475 rv = False 

2476 loc = binding.location 

2477 break 

2478 elif not self.universal and result.value: 

2479 rv = True 

2480 loc = binding.location 

2481 break 

2482 

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

2484 

2485 def can_be_null(self): 

2486 return False 

2487 

2488 def uses_field_access(self): 

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

2490 

2491 

2492############################################################################## 

2493# AST Nodes (Entities) 

2494############################################################################## 

2495 

2496 

2497class Entity(Node, metaclass=ABCMeta): 

2498 """Base class for all entities. 

2499 

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

2501 allocate memory. Examples of entities are types and record 

2502 objects. 

2503 

2504 :attribute name: unqualified name of the entity 

2505 :type: str 

2506 

2507 """ 

2508 

2509 def __init__(self, name, location): 

2510 # lobster-trace: LRM.Described_Name_Equality 

2511 super().__init__(location) 

2512 assert isinstance(name, str) 

2513 self.name = name 

2514 

2515 

2516class Typed_Entity(Entity, metaclass=ABCMeta): 

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

2518 

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

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

2521 are record objects and components. 

2522 

2523 :attribute n_typ: type of the entity 

2524 :type: Type 

2525 

2526 """ 

2527 

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

2529 # lobster-exclude: Constructor only declares variables 

2530 super().__init__(name, location) 

2531 assert isinstance(n_typ, Type) 

2532 self.n_typ = n_typ 

2533 

2534 

2535class Quantified_Variable(Typed_Entity): 

2536 """Variable used in quantified expression. 

2537 

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

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

2540 

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

2542 ^ 

2543 

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

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

2546 

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

2548 :type: Type 

2549 

2550 """ 

2551 

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

2553 # lobster-exclude: Debugging feature 

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

2555 self.n_typ.dump(indent + 1) 

2556 

2557 

2558class Type(Entity, metaclass=ABCMeta): 

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

2560 

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

2562 assert isinstance(mh, Message_Handler) 

2563 assert isinstance(value, Expression) 

2564 assert isinstance(gstab, Symbol_Table) 

2565 return True 

2566 

2567 def get_example_value(self): 

2568 # lobster-exclude: utility method 

2569 assert False 

2570 

2571 

2572class Concrete_Type(Type, metaclass=ABCMeta): 

2573 # lobster-trace: LRM.Type_Declarations 

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

2575 

2576 :attribute n_package: package where this type was declared 

2577 :type: Package 

2578 """ 

2579 

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

2581 super().__init__(name, location) 

2582 assert isinstance(n_package, Package) 

2583 self.n_package = n_package 

2584 

2585 def fully_qualified_name(self): 

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

2587 

2588 :returns: the type's full name 

2589 :rtype: str 

2590 """ 

2591 return self.n_package.name + "." + self.name 

2592 

2593 def __hash__(self): 

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

2595 

2596 def __repr__(self): 

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

2598 

2599 

2600class Builtin_Type(Type, metaclass=ABCMeta): 

2601 # lobster-trace: LRM.Builtin_Types 

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

2603 

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

2605 

2606 def __init__(self, name): 

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

2608 

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

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

2611 

2612 

2613class Builtin_Numeric_Type(Builtin_Type, metaclass=ABCMeta): 

2614 # lobster-trace: LRM.Builtin_Types 

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

2616 

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

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

2619 

2620 

2621class Builtin_Function(Entity): 

2622 # lobster-trace: LRM.Builtin_Functions 

2623 """Builtin functions. 

2624 

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

2626 

2627 :attribute arity: number of parameters 

2628 :type: int 

2629 

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

2631 :type: bool 

2632 

2633 """ 

2634 

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

2636 

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

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

2639 assert isinstance(arity, int) 

2640 assert isinstance(arity_at_least, bool) 

2641 assert arity >= 0 

2642 self.arity = arity 

2643 self.arity_at_least = arity_at_least 

2644 

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

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

2647 

2648 

2649class Array_Type(Type): 

2650 """Anonymous array type. 

2651 

2652 These are declared implicitly for each record component that has 

2653 an array specifier:: 

2654 

2655 foo Integer [5 .. *] 

2656 ^ 

2657 

2658 :attribute lower_bound: minimum number of elements 

2659 :type: int 

2660 

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

2662 :type: Location 

2663 

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

2665 :type: int 

2666 

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

2668 :type: Location 

2669 

2670 :attribute element_type: type of the array elements 

2671 :type: Type 

2672 

2673 """ 

2674 

2675 def __init__( 

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

2677 ): 

2678 # lobster-exclude: Constructor only declares variables 

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

2680 assert isinstance(lower_bound, int) 

2681 assert lower_bound >= 0 

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

2683 assert upper_bound is None or upper_bound >= 0 

2684 assert isinstance(loc_lower, Location) 

2685 assert isinstance(loc_upper, Location) 

2686 

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

2688 name = "universal array" 

2689 elif upper_bound is None: 

2690 if lower_bound == 0: 

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

2692 else: 

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

2694 elif lower_bound == upper_bound: 

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

2696 else: 

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

2698 lower_bound, 

2699 upper_bound, 

2700 element_type.name, 

2701 ) 

2702 super().__init__(name, location) 

2703 self.lower_bound = lower_bound 

2704 self.loc_lower = loc_lower 

2705 self.upper_bound = upper_bound 

2706 self.loc_upper = loc_upper 

2707 self.element_type = element_type 

2708 

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

2710 # lobster-exclude: Debugging feature 

2711 self.write_indent(indent, "Array_Type") 

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

2713 if self.upper_bound is None: 

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

2715 else: 

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

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

2718 

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

2720 assert isinstance(mh, Message_Handler) 

2721 assert isinstance(gstab, Symbol_Table) 

2722 

2723 if isinstance(value, Array_Aggregate): 

2724 return all( 

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

2726 ) 

2727 else: 

2728 assert isinstance(value, Implicit_Null) 

2729 return True 

2730 

2731 def get_example_value(self): 

2732 # lobster-exclude: utility method 

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

2734 

2735 

2736class Union_Type(Type): 

2737 # lobster-trace: LRM.union_type 

2738 # lobster-trace: LRM.Union_Type_Minimum_Members 

2739 # lobster-trace: LRM.Union_Type_Record_Types_Only 

2740 """Anonymous union type for record references. 

2741 

2742 These are declared implicitly when a record component specifies 

2743 multiple allowed record types using bracket syntax:: 

2744 

2745 parent [Systemrequirement, Codebeamerrequirement] 

2746 ^ 

2747 

2748 :attribute types: the allowed record types 

2749 :type: list[Record_Type] 

2750 

2751 """ 

2752 

2753 def __init__(self, location, types): 

2754 assert isinstance(types, list) 

2755 assert len(types) >= 1 

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

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

2758 super().__init__(name, location) 

2759 self.types = types 

2760 self._field_map = None 

2761 

2762 def get_field_map(self): 

2763 # lobster-trace: LRM.Union_Type_Field_Access 

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

2765 

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

2767 

2768 * ``component``: a representative Composite_Component 

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

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

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

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

2773 

2774 :rtype: dict[str, dict] 

2775 """ 

2776 if self._field_map is not None: 

2777 return self._field_map 

2778 

2779 field_map = {} 

2780 for record_type in self.types: 

2781 seen_in_type = set() 

2782 for comp in record_type.all_components(): 

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

2784 continue 

2785 seen_in_type.add(comp.name) 

2786 if comp.name not in field_map: 

2787 field_map[comp.name] = { 

2788 "component": comp, 

2789 "n_typ": comp.n_typ, 

2790 "count": 1, 

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

2792 "optional_in_any": comp.optional, 

2793 } 

2794 else: 

2795 info = field_map[comp.name] 

2796 info["count"] += 1 

2797 # Type identity (is) is correct here: 

2798 # non-union type objects are structural 

2799 # singletons in the symbol table, so 

2800 # identity comparison is both correct and 

2801 # cheap. 

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

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

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

2805 info["optional_in_any"] = True 

2806 

2807 self._field_map = field_map 

2808 return self._field_map 

2809 

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

2811 # lobster-exclude: Debugging feature 

2812 self.write_indent(indent, "Union_Type") 

2813 for t in self.types: 

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

2815 

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

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

2818 # happens in Record_Reference.resolve_references() via 

2819 # is_compatible(). Returning True unconditionally is 

2820 # intentional. 

2821 assert isinstance(mh, Message_Handler) 

2822 assert isinstance(value, Expression) 

2823 assert isinstance(gstab, Symbol_Table) 

2824 return True 

2825 

2826 def is_compatible(self, record_type): 

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

2828 

2829 :param record_type: type to check 

2830 :type record_type: Record_Type 

2831 

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

2833 :rtype: bool 

2834 """ 

2835 assert isinstance(record_type, Record_Type) 

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

2837 

2838 def get_example_value(self): 

2839 # lobster-exclude: utility method 

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

2841 

2842 

2843class Builtin_Integer(Builtin_Numeric_Type): 

2844 # lobster-trace: LRM.Builtin_Types 

2845 # lobster-trace: LRM.Integer_Values 

2846 """Builtin integer type.""" 

2847 

2848 def __init__(self): 

2849 super().__init__("Integer") 

2850 

2851 def get_example_value(self): 

2852 # lobster-exclude: utility method 

2853 return "100" 

2854 

2855 

2856class Builtin_Decimal(Builtin_Numeric_Type): 

2857 # lobster-trace: LRM.Builtin_Types 

2858 # lobster-trace: LRM.Decimal_Values 

2859 """Builtin decimal type.""" 

2860 

2861 def __init__(self): 

2862 super().__init__("Decimal") 

2863 

2864 def get_example_value(self): 

2865 # lobster-exclude: utility method 

2866 return "3.14" 

2867 

2868 

2869class Builtin_Boolean(Builtin_Type): 

2870 # lobster-trace: LRM.Builtin_Types 

2871 # lobster-trace: LRM.Boolean_Values 

2872 """Builtin boolean type.""" 

2873 

2874 def __init__(self): 

2875 super().__init__("Boolean") 

2876 

2877 def get_example_value(self): 

2878 # lobster-exclude: utility method 

2879 return "true" 

2880 

2881 

2882class Builtin_String(Builtin_Type): 

2883 # lobster-trace: LRM.Builtin_Types 

2884 # lobster-trace: LRM.String_Values 

2885 """Builtin string type.""" 

2886 

2887 def __init__(self): 

2888 super().__init__("String") 

2889 

2890 def get_example_value(self): 

2891 # lobster-exclude: utility method 

2892 return '"potato"' 

2893 

2894 

2895class Builtin_Markup_String(Builtin_String): 

2896 # lobster-trace: LRM.Builtin_Types 

2897 # lobster-trace: LRM.Markup_String_Values 

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

2899 objects. 

2900 """ 

2901 

2902 def __init__(self): 

2903 super().__init__() 

2904 self.name = "Markup_String" 

2905 

2906 def get_example_value(self): 

2907 # lobster-exclude: utility method 

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

2909 

2910 

2911class Package(Entity): 

2912 """Packages. 

2913 

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

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

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

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

2918 

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

2920 trlc file 

2921 :type: bool 

2922 

2923 :attribute symbols: symbol table of the package 

2924 :type: Symbol_Table 

2925 

2926 :attribute sub_packages: direct child packages of this package 

2927 :type: Symbol_Table 

2928 

2929 :attribute parent: the parent package, or None for top-level packages 

2930 :type: Package 

2931 

2932 """ 

2933 

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

2935 # lobster-exclude: Constructor only declares variables 

2936 super().__init__(name, location) 

2937 assert isinstance(builtin_stab, Symbol_Table) 

2938 assert isinstance(declared_late, bool) 

2939 self.symbols = Symbol_Table() 

2940 self.symbols.make_visible(builtin_stab) 

2941 self.declared_late = declared_late 

2942 self.sub_packages = Symbol_Table() 

2943 self.parent = None 

2944 

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

2946 # lobster-exclude: Debugging feature 

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

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

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

2950 

2951 def __repr__(self): 

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

2953 

2954 

2955class Composite_Type(Concrete_Type, metaclass=ABCMeta): 

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

2957 functionality. 

2958 

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

2960 :type: Symbol_Table[Composite_Component] 

2961 

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

2963 :type: str 

2964 

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

2966 inherited checks) 

2967 :type: list[Check] 

2968 

2969 """ 

2970 

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

2972 # lobster-trace: LRM.Described_Name_Description 

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

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

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

2976 

2977 self.components = Symbol_Table(inherited_symbols) 

2978 self.description = description 

2979 self.checks = [] 

2980 

2981 def add_check(self, n_check): 

2982 # lobster-trace: LRM.Check_Evaluation_Order 

2983 assert isinstance(n_check, Check) 

2984 self.checks.append(n_check) 

2985 

2986 def iter_checks(self): 

2987 # lobster-trace: LRM.Check_Evaluation_Order 

2988 yield from self.checks 

2989 

2990 def all_components(self): 

2991 # lobster-exclude: Convenience function 

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

2993 

2994 :rtype: list[Composite_Component] 

2995 """ 

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

2997 

2998 

2999class Composite_Component(Typed_Entity): 

3000 """Component in a record or tuple. 

3001 

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

3003 declared:: 

3004 

3005 type|tuple T { 

3006 foo "blah" optional Boolean 

3007 ^1 ^2 ^3 ^4 

3008 

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

3010 :type: str 

3011 

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

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

3014 :type: Composite_Type 

3015 

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

3017 :type: bool 

3018 

3019 """ 

3020 

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

3022 # lobster-trace: LRM.Described_Name_Description 

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

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

3025 assert isinstance(member_of, Composite_Type) 

3026 assert isinstance(optional, bool) 

3027 self.description = description 

3028 self.member_of = member_of 

3029 self.optional = optional 

3030 

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

3032 # lobster-exclude: Debugging feature 

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

3034 if self.description: 

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

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

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

3038 

3039 def __repr__(self): 

3040 return "%s<%s>" % ( 

3041 self.__class__.__name__, 

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

3043 ) 

3044 

3045 

3046class Record_Type(Composite_Type): 

3047 """A user-defined record type. 

3048 

3049 In this example:: 

3050 

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

3052 ^1 ^2 ^3 

3053 

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

3055 of the :class:`Composite_Type` base. 

3056 

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

3058 :type: Record_Type 

3059 

3060 :attribute frozen: mapping of frozen components 

3061 :type: dict[str, Expression] 

3062 

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

3064 :type: bool 

3065 

3066 :attribute is_abstract: type is abstract 

3067 :type: bool 

3068 

3069 """ 

3070 

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

3072 # lobster-exclude: Constructor only declares variables 

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

3074 assert isinstance(is_abstract, bool) 

3075 super().__init__( 

3076 name, 

3077 description, 

3078 location, 

3079 package, 

3080 n_parent.components if n_parent else None, 

3081 ) 

3082 self.parent = n_parent 

3083 self.frozen = {} 

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

3085 self.is_abstract = is_abstract 

3086 

3087 def iter_checks(self): 

3088 # lobster-trace: LRM.Check_Evaluation_Order 

3089 # lobster-trace: LRM.Check_Evaluation_Order_For_Extensions 

3090 if self.parent: 

3091 yield from self.parent.iter_checks() 

3092 yield from self.checks 

3093 

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

3095 # lobster-exclude: Debugging feature 

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

3097 if self.description: 

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

3099 if self.parent: 

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

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

3102 if self.checks: 

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

3104 for n_check in self.checks: 

3105 n_check.dump(indent + 2) 

3106 else: 

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

3108 

3109 def all_components(self): 

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

3111 

3112 :rtype: list[Composite_Component] 

3113 """ 

3114 if self.parent: 

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

3116 else: 

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

3118 

3119 def is_subclass_of(self, record_type): 

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

3121 

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

3123 :type record_type: Record_Type 

3124 

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

3126 :rtype: Boolean 

3127 """ 

3128 assert isinstance(record_type, Record_Type) 

3129 

3130 ptr = self 

3131 while ptr: 

3132 if ptr is record_type: 

3133 return True 

3134 else: 

3135 ptr = ptr.parent 

3136 return False 

3137 

3138 def is_frozen(self, n_component): 

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

3140 

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

3142 (or any of its parents) 

3143 :type n_component: Composite_Component 

3144 

3145 :rtype: bool 

3146 """ 

3147 assert isinstance(n_component, Composite_Component) 

3148 if n_component.name in self.frozen: 

3149 return True 

3150 elif self.parent: 

3151 return self.parent.is_frozen(n_component) 

3152 else: 

3153 return False 

3154 

3155 def get_freezing_expression(self, n_component): 

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

3157 

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

3159 component that his not frozen. 

3160 

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

3162 (or any of its parents) 

3163 :type n_component: Composite_Component 

3164 

3165 :rtype: Expression 

3166 

3167 """ 

3168 assert isinstance(n_component, Composite_Component) 

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

3170 return self.frozen[n_component.name] 

3171 elif self.parent: 

3172 return self.parent.get_freezing_expression(n_component) 

3173 else: 

3174 assert False 

3175 

3176 def get_example_value(self): 

3177 # lobster-exclude: utility method 

3178 return "%s_instance" % self.name 

3179 

3180 

3181class Tuple_Type(Composite_Type): 

3182 """A user-defined tuple type. 

3183 

3184 In this example:: 

3185 

3186 tuple T "optional description of T" { 

3187 ^1 ^2 

3188 

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

3190 of the :class:`Composite_Type` base. 

3191 

3192 :attribute separators: list of syntactic separators. 

3193 :type: list[Separator] 

3194 

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

3196 precisely one less separator than components. 

3197 

3198 """ 

3199 

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

3201 # lobster-trace: LRM.Tuple_Declaration 

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

3203 self.separators = [] 

3204 

3205 def add_separator(self, n_separator): 

3206 # lobster-exclude: utility method 

3207 assert isinstance(n_separator, Separator) 

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

3209 self.separators.append(n_separator) 

3210 

3211 def iter_separators(self): 

3212 """Iterate over all separators""" 

3213 # lobster-exclude: utility method 

3214 yield from self.separators 

3215 

3216 def iter_sequence(self): 

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

3218 # lobster-exclude: utility method 

3219 if self.separators: 

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

3221 yield n_component 

3222 if i < len(self.separators): 

3223 yield self.separators[i] 

3224 else: 

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

3226 

3227 def has_separators(self): 

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

3229 # lobster-exclude: utility method 

3230 return bool(self.separators) 

3231 

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

3233 # lobster-exclude: Debugging feature 

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

3235 if self.description: 

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

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

3238 for n_item in self.iter_sequence(): 

3239 n_item.dump(indent + 2) 

3240 if self.checks: 

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

3242 for n_check in self.checks: 

3243 n_check.dump(indent + 2) 

3244 else: 

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

3246 

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

3248 # lobster-trace: LRM.Check_Evaluation_Order 

3249 assert isinstance(mh, Message_Handler) 

3250 assert isinstance(gstab, Symbol_Table) 

3251 

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

3253 ok = True 

3254 for check in self.iter_checks(): 

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

3256 ok = False 

3257 return ok 

3258 else: 

3259 assert isinstance(value, Implicit_Null) 

3260 return True 

3261 

3262 def get_example_value(self): 

3263 # lobster-exclude: utility method 

3264 parts = [] 

3265 for n_item in self.iter_sequence(): 

3266 if isinstance(n_item, Composite_Component): 

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

3268 else: 

3269 parts.append(n_item.to_string()) 

3270 if self.has_separators(): 

3271 return " ".join(parts) 

3272 else: 

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

3274 

3275 

3276class Separator(Node): 

3277 # lobster-trace: LRM.Tuple_Declaration 

3278 """User-defined syntactic separator 

3279 

3280 For example:: 

3281 

3282 separator x 

3283 ^1 

3284 

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

3286 :type: Token 

3287 """ 

3288 

3289 def __init__(self, token): 

3290 super().__init__(token.location) 

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

3292 "IDENTIFIER", 

3293 "AT", 

3294 "COLON", 

3295 "SEMICOLON", 

3296 ) 

3297 self.token = token 

3298 

3299 def to_string(self): 

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

3301 self.token.kind, self.token.value 

3302 ) 

3303 

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

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

3306 

3307 

3308class Enumeration_Type(Concrete_Type): 

3309 """User-defined enumeration types. 

3310 

3311 For example:: 

3312 

3313 enum T "potato" { 

3314 ^1 ^2 

3315 

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

3317 :type: str 

3318 

3319 :attribute literals: the literals in this enumeration 

3320 :type: Symbol_Table[Enumeration_Literal_Spec] 

3321 

3322 """ 

3323 

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

3325 # lobster-trace: LRM.Described_Name_Description 

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

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

3328 self.literals = Symbol_Table() 

3329 self.description = description 

3330 

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

3332 # lobster-exclude: Debugging feature 

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

3334 if self.description: 

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

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

3337 

3338 def get_example_value(self): 

3339 # lobster-exclude: utility method 

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

3341 if options: 

3342 choice = len(options) // 2 

3343 return self.name + "." + choice.name 

3344 else: 

3345 return "ERROR" 

3346 

3347 

3348class Enumeration_Literal_Spec(Typed_Entity): 

3349 """Declared literal in an enumeration declaration. 

3350 

3351 Note that for literals mentioned later in record object 

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

3353 are used here:: 

3354 

3355 enum ASIL { 

3356 QM "not safety related" 

3357 ^1 ^2 

3358 

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

3360 :type: str 

3361 

3362 """ 

3363 

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

3365 # lobster-trace: LRM.Described_Name_Description 

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

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

3368 assert isinstance(enum, Enumeration_Type) 

3369 self.description = description 

3370 

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

3372 # lobster-exclude: Debugging feature 

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

3374 if self.description: 

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

3376 

3377 

3378class Record_Object(Typed_Entity): 

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

3380 

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

3382 

3383 section "Potato" { 

3384 ^5 

3385 Requirement PotatoReq { 

3386 ^1 ^2 

3387 component1 = 42 

3388 ^3 ^4 

3389 

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

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

3392 class. 

3393 

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

3395 :type: dict[str, Expression] 

3396 

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

3398 :type: Section 

3399 

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

3401 :type: Section 

3402 

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

3404 to: 

3405 

3406 * :class:`Literal` 

3407 * :class:`Unary_Expression` 

3408 * :class:`Array_Aggregate` 

3409 * :class:`Tuple_Aggregate` 

3410 * :class:`Record_Reference` 

3411 * :class:`Implicit_Null` 

3412 

3413 """ 

3414 

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

3416 # lobster-trace: LRM.Section_Declaration 

3417 # lobster-trace: LRM.Unspecified_Optional_Components 

3418 # lobster-trace: LRM.Record_Object_Declaration 

3419 

3420 assert isinstance(n_typ, Record_Type) 

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

3422 assert isinstance(n_package, Package) 

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

3424 self.field = { 

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

3426 } 

3427 self.section = section 

3428 self.n_package = n_package 

3429 

3430 def fully_qualified_name(self): 

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

3432 

3433 :returns: the object's full name 

3434 :rtype: str 

3435 """ 

3436 return self.n_package.name + "." + self.name 

3437 

3438 def to_python_dict(self): 

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

3440 

3441 For example it might provide:: 

3442 

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

3444 "bar" : None, 

3445 "baz" : "value"} 

3446 

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

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

3449 

3450 """ 

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

3452 

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

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

3455 

3456 def assign(self, component, value): 

3457 assert isinstance(component, Composite_Component) 

3458 assert isinstance( 

3459 value, 

3460 ( 

3461 Literal, 

3462 Array_Aggregate, 

3463 Tuple_Aggregate, 

3464 Record_Reference, 

3465 Implicit_Null, 

3466 Unary_Expression, 

3467 ), 

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

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

3470 raise KeyError( 

3471 f"Component {component.name} already \ 

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

3473 ) 

3474 self.field[component.name] = value 

3475 

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

3477 # lobster-exclude: Debugging feature 

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

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

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

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

3482 value.dump(indent + 2) 

3483 if self.section: 

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

3485 

3486 def resolve_references(self, mh): 

3487 assert isinstance(mh, Message_Handler) 

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

3489 val.resolve_references(mh) 

3490 

3491 def perform_checks(self, mh, gstab): 

3492 # lobster-trace: LRM.Check_Evaluation_Order 

3493 # lobster-trace: LRM.Evaluation_Of_Checks 

3494 assert isinstance(mh, Message_Handler) 

3495 assert isinstance(gstab, Symbol_Table) 

3496 

3497 ok = True 

3498 

3499 # First evaluate all tuple checks 

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

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

3502 ok = False 

3503 

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

3505 

3506 # Then evaluate all record checks 

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

3508 # Prints messages, if applicable. Raises exception on 

3509 # fatal checks, which causes this to abort. 

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

3511 ok = False 

3512 

3513 return ok 

3514 

3515 def __repr__(self): 

3516 return "%s<%s>" % ( 

3517 self.__class__.__name__, 

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

3519 ) 

3520 

3521 

3522class Section(Entity): 

3523 # lobster-trace: LRM.Section_Declaration 

3524 """A section for readability 

3525 

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

3527 objects together:: 

3528 

3529 section "Foo" { 

3530 ^^^^^ parent section 

3531 section "Bar" { 

3532 ^^^^^ section 

3533 

3534 :attribute parent: the parent section or None 

3535 :type: Section 

3536 

3537 """ 

3538 

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

3540 super().__init__(name, location) 

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

3542 self.parent = parent 

3543 

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

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

3546 if self.parent is None: 

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

3548 else: 

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

3550 

3551 

3552############################################################################## 

3553# Symbol Table & Scopes 

3554############################################################################## 

3555 

3556 

3557class Symbol_Table: 

3558 """Symbol table mapping names to entities""" 

3559 

3560 def __init__(self, parent=None): 

3561 # lobster-exclude: Constructor only declares variables 

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

3563 self.parent = parent 

3564 self.imported = [] 

3565 self.table = OrderedDict() 

3566 self.trlc_files = [] 

3567 self.section_names = [] 

3568 

3569 @staticmethod 

3570 def simplified_name(name): 

3571 # lobster-trace: LRM.Sufficiently_Distinct 

3572 assert isinstance(name, str) 

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

3574 

3575 def all_names(self): 

3576 # lobster-exclude: API for users 

3577 """All names in the symbol table 

3578 

3579 :rtype: set[str] 

3580 """ 

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

3582 if self.parent: 

3583 rv |= self.parent.all_names() 

3584 return rv 

3585 

3586 def iter_record_objects_by_section(self): 

3587 """API for users 

3588 

3589 Retriving information about the section hierarchy for record objects 

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

3591 sub sections and record objects 

3592 Output: Information about sections and level of sections, 

3593 record objects and levels of record object 

3594 """ 

3595 for record_object in self.iter_record_objects(): 

3596 location = record_object.location.file_name 

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

3598 self.trlc_files.append(location) 

3599 yield location 

3600 if record_object.section: 

3601 object_level = len(record_object.section) - 1 

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

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

3604 self.section_names.append(section) 

3605 yield section.name, level 

3606 yield record_object, object_level 

3607 else: 

3608 object_level = 0 

3609 yield record_object, object_level 

3610 

3611 def iter_record_objects(self): 

3612 # lobster-exclude: API for users 

3613 """Iterate over all record objects 

3614 

3615 :rtype: iterable[Record_Object] 

3616 """ 

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

3618 if isinstance(item, Package): 

3619 yield from item.symbols.iter_record_objects() 

3620 

3621 elif isinstance(item, Record_Object): 

3622 yield item 

3623 

3624 def values(self, subtype=None): 

3625 # lobster-exclude: API for users 

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

3627 if self.parent: 

3628 yield from self.parent.values(subtype) 

3629 for name in sorted(self.table): 

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

3631 yield self.table[name] 

3632 

3633 def make_visible(self, stab): 

3634 assert isinstance(stab, Symbol_Table) 

3635 self.imported.append(stab) 

3636 

3637 def register(self, mh, entity): 

3638 # lobster-trace: LRM.Duplicate_Types 

3639 # lobster-trace: LRM.Unique_Enumeration_Literals 

3640 # lobster-trace: LRM.Tuple_Unique_Field_Names 

3641 # lobster-trace: LRM.Sufficiently_Distinct 

3642 # lobster-trace: LRM.Unique_Object_Names 

3643 

3644 assert isinstance(mh, Message_Handler) 

3645 assert isinstance(entity, Entity) 

3646 

3647 simple_name = self.simplified_name(entity.name) 

3648 

3649 if self.contains_raw(simple_name): 

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

3651 if pdef.name == entity.name: 

3652 mh.error( 

3653 entity.location, 

3654 "duplicate definition, previous definition at %s" 

3655 % mh.cross_file_reference(pdef.location), 

3656 ) 

3657 else: 

3658 mh.error( 

3659 entity.location, 

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

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

3662 ) 

3663 

3664 else: 

3665 self.table[simple_name] = entity 

3666 

3667 def register_with_key(self, mh, entity, key_name): 

3668 """Register an entity under a different key name (leaf name). 

3669 

3670 Used to register nested packages in their parent's ``sub_packages`` 

3671 table keyed by their leaf segment rather than their full name. 

3672 

3673 :param mh: The message handler to use 

3674 :type mh: Message_Handler 

3675 

3676 :param entity: the entity to register 

3677 :type entity: Entity 

3678 

3679 :param key_name: the name to use as the lookup key 

3680 :type key_name: str 

3681 """ 

3682 # lobster-trace: LRM.Sufficiently_Distinct 

3683 assert isinstance(mh, Message_Handler) 

3684 assert isinstance(entity, Entity) 

3685 assert isinstance(key_name, str) 

3686 

3687 simple_key = self.simplified_name(key_name) 

3688 

3689 if simple_key in self.table: 

3690 existing = self.table[simple_key] 

3691 if existing.name == entity.name: 3691 ↛ 3700line 3691 didn't jump to line 3700 because the condition on line 3691 was always true

3692 mh.error( 

3693 entity.location, 

3694 "duplicate definition, previous definition at %s" 

3695 % mh.cross_file_reference(existing.location), 

3696 explanation="rename or remove one of the two " 

3697 "declarations so each sub-package has a unique name", 

3698 ) 

3699 else: 

3700 mh.error( 

3701 entity.location, 

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

3703 % ( 

3704 entity.name, 

3705 existing.name, 

3706 mh.cross_file_reference(existing.location), 

3707 ), 

3708 explanation="package names must be sufficiently " 

3709 "distinct (case and underscores are ignored); " 

3710 "rename one of the two packages", 

3711 ) 

3712 else: 

3713 self.table[simple_key] = entity 

3714 

3715 def lookup_sub_package(self, segment): 

3716 """Look up a direct child package by its leaf segment name. 

3717 

3718 Used for traversing the nested package hierarchy, where the table 

3719 key is the simplified leaf segment but the entity name is the full 

3720 dotted package name. 

3721 

3722 :param segment: the leaf name to look for (e.g. ``"bar"`` for \ 

3723 ``foo.bar``) 

3724 :type segment: str 

3725 

3726 :returns: the child Package, or None if not found 

3727 :rtype: Package or None 

3728 """ 

3729 assert isinstance(segment, str) 

3730 simple_key = self.simplified_name(segment) 

3731 if simple_key in self.table: 

3732 entity = self.table[simple_key] 

3733 if isinstance(entity, Package): 3733 ↛ 3735line 3733 didn't jump to line 3735 because the condition on line 3733 was always true

3734 return entity 

3735 return None 

3736 

3737 def __contains__(self, name): 

3738 # lobster-trace: LRM.Described_Name_Equality 

3739 return self.contains(name) 

3740 

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

3742 # lobster-trace: LRM.Described_Name_Equality 

3743 # lobster-trace: LRM.Sufficiently_Distinct 

3744 # 

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

3746 # table. 

3747 assert isinstance(simple_name, str) 

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

3749 

3750 if simple_name in self.table: 

3751 # No need to continue searching since registering a 

3752 # clashing name would have been stopped 

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

3754 

3755 elif self.parent: 

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

3757 

3758 for stab in self.imported: 

3759 if stab.contains_raw(simple_name, precise_name): 

3760 return True 

3761 

3762 return False 

3763 

3764 def contains(self, name): 

3765 # lobster-trace: LRM.Described_Name_Equality 

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

3767 

3768 :param name: the name to test 

3769 :type name: str 

3770 

3771 :rtype: bool 

3772 """ 

3773 assert isinstance(name, str) 

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

3775 

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

3777 # lobster-trace: LRM.Described_Name_Equality 

3778 # lobster-trace: LRM.Sufficiently_Distinct 

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

3780 

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

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

3783 

3784 :param mh: The message handler to use 

3785 :type mh: Message_Handler 

3786 

3787 :param name: The name to search for 

3788 :type name: str 

3789 

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

3791 is not an instance of the given class 

3792 :type required_subclass: type 

3793 

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

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

3796 :rtype: Entity 

3797 

3798 """ 

3799 assert isinstance(mh, Message_Handler) 

3800 assert isinstance(name, str) 

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

3802 

3803 simple_name = self.simplified_name(name) 

3804 

3805 ptr = self 

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

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

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

3809 rv = ptr.table[simple_name] 

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

3811 return None 

3812 

3813 if required_subclass is not None and not isinstance( 3813 ↛ 3816line 3813 didn't jump to line 3816 because the condition on line 3813 was never true

3814 rv, required_subclass 

3815 ): 

3816 mh.error( 

3817 rv.location, 

3818 "%s %s is not a %s" 

3819 % (rv.__class__.__name__, name, required_subclass.__name__), 

3820 ) 

3821 return rv 

3822 else: 

3823 ptr = ptr.parent 

3824 

3825 return None 

3826 

3827 def lookup_direct( 

3828 self, mh, name, error_location, required_subclass=None, simplified=False 

3829 ): 

3830 # lobster-trace: LRM.Described_Name_Equality 

3831 # lobster-trace: LRM.Sufficiently_Distinct 

3832 # lobster-trace: LRM.Valid_Base_Names 

3833 # lobster-trace: LRM.Valid_Access_Prefixes 

3834 # lobster-trace: LRM.Valid_Function_Prefixes 

3835 """Retrieve an object from the table 

3836 

3837 For example:: 

3838 

3839 pkg = stab.lookup_direct(mh, 

3840 "potato", 

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

3842 Package) 

3843 

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

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

3846 Package, then the following error is issued:: 

3847 

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

3849 

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

3851 

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

3853 

3854 :param mh: The message handler to use 

3855 :type mh: Message_Handler 

3856 

3857 :param name: The name to search for 

3858 :type name: str 

3859 

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

3861 not found 

3862 :type error_location: Location 

3863 

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

3865 is not an instance of the given class 

3866 :type required_subclass: type 

3867 

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

3869 of the actual name 

3870 :type simplified: bool 

3871 

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

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

3874 :returns: the specified entity 

3875 :rtype: Entity 

3876 

3877 """ 

3878 assert isinstance(mh, Message_Handler) 

3879 assert isinstance(name, str) 

3880 assert isinstance(error_location, Location) 

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

3882 assert isinstance(simplified, bool) 

3883 

3884 simple_name = self.simplified_name(name) 

3885 ptr = self 

3886 options = [] 

3887 

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

3889 while ptr: 

3890 if simple_name in ptr.table: 

3891 rv = ptr.table[simple_name] 

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

3893 mh.error( 

3894 error_location, 

3895 "unknown symbol %s, did you mean %s?" % (name, rv.name), 

3896 ) 

3897 

3898 if required_subclass is not None and not isinstance( 

3899 rv, required_subclass 

3900 ): 

3901 mh.error( 

3902 error_location, 

3903 "%s %s is not a %s" 

3904 % (rv.__class__.__name__, name, required_subclass.__name__), 

3905 ) 

3906 return rv 

3907 else: 

3908 options += list(item.name for item in ptr.table.values()) 

3909 ptr = ptr.parent 

3910 

3911 matches = get_close_matches(word=name, possibilities=options, n=1) 

3912 

3913 if matches: 

3914 mh.error( 

3915 error_location, 

3916 "unknown symbol %s, did you mean %s?" % (name, matches[0]), 

3917 ) 

3918 else: 

3919 mh.error(error_location, "unknown symbol %s" % name) 

3920 

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

3922 # lobster-trace: LRM.Described_Name_Equality 

3923 assert isinstance(mh, Message_Handler) 

3924 assert isinstance(referencing_token, Token) 

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

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

3927 

3928 return self.lookup_direct( 

3929 mh=mh, 

3930 name=referencing_token.value, 

3931 error_location=referencing_token.location, 

3932 required_subclass=required_subclass, 

3933 ) 

3934 

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

3936 # lobster-exclude: Debugging feature 

3937 assert isinstance(indent, int) 

3938 assert indent >= 0 

3939 assert isinstance(message, str) 

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

3941 

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

3943 # lobster-exclude: Debugging feature 

3944 if omit_heading: 

3945 new_indent = indent 

3946 else: 

3947 self.write_indent(indent, "Symbol_Table") 

3948 new_indent = indent + 1 

3949 ptr = self 

3950 while ptr: 

3951 for name in ptr.table: 

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

3953 ptr = ptr.parent 

3954 

3955 @classmethod 

3956 def create_global_table(cls, mh): 

3957 # lobster-trace: LRM.Builtin_Types 

3958 # lobster-trace: LRM.Builtin_Functions 

3959 # lobster-trace: LRM.Builtin_Type_Conversion_Functions 

3960 # lobster-trace: LRM.Signature_Len 

3961 # lobster-trace: LRM.Signature_String_End_Functions 

3962 # lobster-trace: LRM.Signature_Matches 

3963 

3964 stab = Symbol_Table() 

3965 stab.register(mh, Builtin_Integer()) 

3966 stab.register(mh, Builtin_Decimal()) 

3967 stab.register(mh, Builtin_Boolean()) 

3968 stab.register(mh, Builtin_String()) 

3969 stab.register(mh, Builtin_Markup_String()) 

3970 stab.register(mh, Builtin_Function("len", 1)) 

3971 stab.register(mh, Builtin_Function("startswith", 2)) 

3972 stab.register(mh, Builtin_Function("endswith", 2)) 

3973 stab.register(mh, Builtin_Function("matches", 2)) 

3974 stab.register(mh, Builtin_Function("oneof", 1, arity_at_least=True)) 

3975 

3976 return stab 

3977 

3978 

3979class Scope: 

3980 def __init__(self): 

3981 # lobster-exclude: Constructor only declares variables 

3982 self.scope = [] 

3983 

3984 def push(self, stab): 

3985 assert isinstance(stab, Symbol_Table) 

3986 self.scope.append(stab) 

3987 

3988 def pop(self): 

3989 self.scope.pop() 

3990 

3991 def contains(self, name): 

3992 assert isinstance(name, str) 

3993 

3994 for stab in reversed(self.scope): 

3995 if stab.contains(name): 

3996 return True 

3997 return False 

3998 

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

4000 assert len(self.scope) >= 1 

4001 assert isinstance(mh, Message_Handler) 

4002 assert isinstance(referencing_token, Token) 

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

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

4005 

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

4007 if stab.contains(referencing_token.value): 

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

4009 return self.scope[0].lookup(mh, referencing_token, required_subclass) 

4010 

4011 def size(self): 

4012 return len(self.scope)