Coverage for trlc/ast.py: 90%

1199 statements  

« prev     ^ index     » next       coverage.py v7.10.6, created at 2025-09-05 17:22 +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 Florian Schanda 

6# 

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

8# 

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

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

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

12# (at your option) any later version. 

13# 

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

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

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

17# License for more details. 

18# 

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

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

21 

22from abc import ABCMeta, abstractmethod 

23import re 

24 

25from copy import copy 

26from difflib import get_close_matches 

27from enum import Enum, auto 

28from collections import OrderedDict 

29from fractions import Fraction 

30 

31from trlc.errors import TRLC_Error, Location, Message_Handler 

32from trlc.lexer import Token 

33from trlc import math 

34 

35# 

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

37# reference implementation. There are four sections: 

38# 

39# - Valuations deal with concrete values for record objects 

40# - AST expressions deal with the syntax tree 

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

42# - Symbol_Table and scope deals with name resolution 

43# 

44 

45 

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

47# Valuations 

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

49 

50class Value: 

51 # lobster-trace: LRM.Boolean_Values 

52 # lobster-trace: LRM.Integer_Values 

53 # lobster-trace: LRM.Decimal_Values 

54 # lobster-trace: LRM.String_Values 

55 # lobster-trace: LRM.Markup_String_Values 

56 """Polymorphic value for evaluating expressions. 

57 

58 Any record references will be fully resolved. 

59 

60 :attribute location: source location this value comes from 

61 :type: Location 

62 

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

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

65 Record_Reference, Enumeration_Literal_Spec 

66 

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

68 :type: Type 

69 """ 

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

71 assert isinstance(location, Location) 

72 assert value is None or \ 

73 isinstance(value, (str, 

74 int, 

75 bool, 

76 list, # for arrays 

77 dict, # for tuples 

78 Fraction, 

79 Record_Reference, 

80 Enumeration_Literal_Spec)) 

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

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

83 

84 self.location = location 

85 self.value = value 

86 self.typ = typ 

87 

88 def __eq__(self, other): 

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

90 

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

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

93 

94 def resolve_references(self, mh): 

95 assert isinstance(mh, Message_Handler) 

96 

97 if isinstance(self.value, Record_Reference): 

98 self.value.resolve(mh) 

99 

100 

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

102# AST Nodes 

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

104 

105class Node(metaclass=ABCMeta): 

106 """Base class for all AST items. 

107 

108 :attribute location: source location 

109 :type: Location 

110 """ 

111 def __init__(self, location): 

112 # lobster-exclude: Constructor only declares variables 

113 assert isinstance(location, Location) 

114 self.location = location 

115 

116 def set_ast_link(self, tok): 

117 assert isinstance(tok, Token) 

118 tok.ast_link = self 

119 

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

121 # lobster-exclude: Debugging feature 

122 assert isinstance(indent, int) 

123 assert indent >= 0 

124 assert isinstance(message, str) 

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

126 

127 @abstractmethod 

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

129 """Visualise the parse tree. 

130 

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

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

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

134 output like this:: 

135 

136 Symbol_Table 

137 Builtin_Boolean 

138 Builtin_Integer 

139 Builtin_Decimal 

140 Builtin_String 

141 Builtin_Markup_String 

142 Package bar 

143 Symbol_Table 

144 Record_Type MyType 

145 Composite_Component name 

146 Optional: False 

147 Type: String 

148 Checks 

149 Error 'description is too short' 

150 Anchor: description 

151 Binary Binary_Operator.COMP_GT Expression 

152 Type: Boolean 

153 Unary Unary_Operator.STRING_LENGTH Expression 

154 Type: Integer 

155 Name Reference to description 

156 Integer Literal 10 

157 Package instances 

158 Symbol_Table 

159 Record_Object SomeThing 

160 Type: MyType 

161 Field description: "Potato" 

162 Builtin_Function endswith 

163 Builtin_Function len 

164 Builtin_Function matches 

165 Builtin_Function startswith 

166 Builtin_Function oneof 

167 

168 """ 

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

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

171 # lobster-exclude: Debugging feature 

172 

173 

174class Check_Block(Node): 

175 """Node representing check blocks 

176 

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

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

179 

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

181 :type: Composite_Type 

182 

183 :attribute checks: list of checks 

184 :type: list[Check] 

185 

186 """ 

187 def __init__(self, location, n_typ): 

188 # lobster-trace: LRM.Check_Block 

189 super().__init__(location) 

190 assert isinstance(n_typ, Composite_Type) 

191 self.n_typ = n_typ 

192 self.checks = [] 

193 

194 def add_check(self, n_check): 

195 # lobster-trace: LRM.Check_Evaluation_Order 

196 assert isinstance(n_check, Check) 

197 self.checks.append(n_check) 

198 

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

200 # lobster-exclude: Debugging feature 

201 self.write_indent(indent, "Check_Block") 

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

203 for n_check in self.checks: 

204 n_check.dump(indent + 1) 

205 

206 

207class Compilation_Unit(Node): 

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

209 

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

211 :type: Package 

212 

213 :attribute imports: package imported by this file 

214 :type: list[Package] 

215 

216 :attribute items: list of 

217 :type: list[Node] 

218 

219 """ 

220 def __init__(self, file_name): 

221 # lobster-exclude: Constructor only declares variables 

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

223 self.package = None 

224 self.imports = None 

225 self.raw_imports = [] 

226 self.items = [] 

227 

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

229 # lobster-exclude: Debugging feature 

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

231 for t_import in self.raw_imports: 

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

233 for n_item in self.items: 

234 n_item.dump(indent + 1) 

235 

236 def set_package(self, pkg): 

237 # lobster-trace: LRM.Current_Package 

238 assert isinstance(pkg, Package) 

239 self.package = pkg 

240 

241 def add_import(self, mh, t_import): 

242 # lobster-trace: LRM.Import_Visibility 

243 # lobster-trace: LRM.Self_Imports 

244 assert isinstance(mh, Message_Handler) 

245 assert isinstance(t_import, Token) 

246 assert t_import.kind == "IDENTIFIER" 

247 

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

249 mh.error(t_import.location, 

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

251 

252 # Skip duplicates 

253 for t_previous in self.raw_imports: 

254 if t_previous.value == t_import.value: 

255 mh.warning(t_import.location, 

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

257 return 

258 

259 self.raw_imports.append(t_import) 

260 

261 def resolve_imports(self, mh, stab): 

262 # lobster-trace: LRM.Import_Visibility 

263 assert isinstance(mh, Message_Handler) 

264 assert isinstance(stab, Symbol_Table) 

265 self.imports = set() 

266 for t_import in self.raw_imports: 

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

268 # generate more error later. 

269 try: 

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

271 self.imports.add(a_import) 

272 a_import.set_ast_link(t_import) 

273 except TRLC_Error: 

274 pass 

275 

276 def is_visible(self, n_pkg): 

277 # lobster-trace: LRM.Import_Visibility 

278 assert self.imports is not None 

279 assert isinstance(n_pkg, Package) 

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

281 

282 def add_item(self, node): 

283 # lobster-trace: LRM.RSL_File 

284 # lobster-trace: LRM.TRLC_File 

285 assert isinstance(node, (Concrete_Type, 

286 Check_Block, 

287 Record_Object)), \ 

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

289 self.items.append(node) 

290 

291 

292class Check(Node): 

293 """User defined check 

294 

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

296 

297 checks T { 

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

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

300 

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

302 :type: Composite_Type 

303 

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

305 :type: Expression 

306 

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

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

309 :type: Composite_Component 

310 

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

312 not specified the default is 'error') 

313 :type: str 

314 

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

316 :type: str 

317 """ 

318 def __init__(self, 

319 n_type, 

320 n_expr, 

321 n_anchor, 

322 severity, 

323 t_message, 

324 extrainfo): 

325 # lobster-trace: LRM.Check_Block 

326 assert isinstance(n_type, Composite_Type) 

327 assert isinstance(n_expr, Expression) 

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

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

330 assert isinstance(t_message, Token) 

331 assert t_message.kind == "STRING" 

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

333 super().__init__(t_message.location) 

334 

335 self.n_type = n_type 

336 self.n_expr = n_expr 

337 self.n_anchor = n_anchor 

338 self.severity = severity 

339 # lobster-trace: LRM.No_Newlines_In_Message 

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

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

342 # raised is non-fatal. 

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

344 self.extrainfo = extrainfo 

345 

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

347 # lobster-exclude: Debugging feature 

348 if self.severity == "warning": 

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

350 elif self.severity == "error": 

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

352 else: 

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

354 if self.n_anchor: 

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

356 self.n_expr.dump(indent + 1) 

357 

358 def get_real_location(self, composite_object): 

359 # lobster-exclude: LRM.Anchoring 

360 assert isinstance(composite_object, (Record_Object, 

361 Tuple_Aggregate)) 

362 if isinstance(composite_object, Record_Object): 

363 fields = composite_object.field 

364 else: 

365 fields = composite_object.value 

366 

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

368 return composite_object.location 

369 else: 

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

371 

372 def perform(self, mh, composite_object): 

373 # lobster-trace: LRM.Check_Messages 

374 # lobster-trace: LRM.Check_Severity 

375 assert isinstance(mh, Message_Handler) 

376 assert isinstance(composite_object, (Record_Object, 

377 Tuple_Aggregate)) 

378 

379 if isinstance(composite_object, Record_Object): 

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

381 else: 

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

383 assert isinstance(result.value, bool) 

384 

385 if not result.value: 

386 loc = self.get_real_location(composite_object) 

387 if self.severity == "warning": 

388 mh.warning(location = loc, 

389 message = self.message, 

390 explanation = self.extrainfo, 

391 user = True) 

392 else: 

393 mh.error(location = loc, 

394 message = self.message, 

395 explanation = self.extrainfo, 

396 fatal = self.severity == "fatal", 

397 user = True) 

398 return False 

399 

400 return True 

401 

402############################################################################## 

403# AST Nodes (Expressions) 

404############################################################################## 

405 

406 

407class Unary_Operator(Enum): 

408 # lobster-exclude: Utility enumeration for unary operators 

409 MINUS = auto() 

410 PLUS = auto() 

411 LOGICAL_NOT = auto() 

412 ABSOLUTE_VALUE = auto() 

413 

414 STRING_LENGTH = auto() 

415 ARRAY_LENGTH = auto() 

416 

417 CONVERSION_TO_INT = auto() 

418 CONVERSION_TO_DECIMAL = auto() 

419 

420 

421class Binary_Operator(Enum): 

422 # lobster-exclude: Utility enumeration for binary operators 

423 LOGICAL_AND = auto() # Short-circuit 

424 LOGICAL_OR = auto() # Short-circuit 

425 LOGICAL_XOR = auto() 

426 LOGICAL_IMPLIES = auto() # Short-circuit 

427 

428 COMP_EQ = auto() 

429 COMP_NEQ = auto() 

430 COMP_LT = auto() 

431 COMP_LEQ = auto() 

432 COMP_GT = auto() 

433 COMP_GEQ = auto() 

434 

435 STRING_CONTAINS = auto() 

436 STRING_STARTSWITH = auto() 

437 STRING_ENDSWITH = auto() 

438 STRING_REGEX = auto() 

439 

440 ARRAY_CONTAINS = auto() 

441 

442 PLUS = auto() 

443 MINUS = auto() 

444 TIMES = auto() 

445 DIVIDE = auto() 

446 REMAINDER = auto() 

447 

448 POWER = auto() 

449 

450 INDEX = auto() 

451 

452 

453class Expression(Node, metaclass=ABCMeta): 

454 """Abstract base class for all expressions. 

455 

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

457 :type: Type 

458 """ 

459 def __init__(self, location, typ): 

460 # lobster-exclude: Constructor only declares variables 

461 super().__init__(location) 

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

463 self.typ = typ 

464 

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

466 """Evaluate the expression in the given context 

467 

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

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

470 dictionary that maps names (such as record fields or 

471 quantified variables) to expressions. 

472 

473 :param mh: the message handler to use 

474 :type mh: Message_Handler 

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

476 :type context: dict[str, Expression] 

477 :raise TRLC_Error: if the expression cannot be evaluated 

478 :return: result of the evaluation 

479 :rtype: Value 

480 """ 

481 assert isinstance(mh, Message_Handler) 

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

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

484 self.__class__.__name__ 

485 

486 @abstractmethod 

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

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

489 self.__class__.__name__ 

490 

491 def ensure_type(self, mh, typ): 

492 # lobster-trace: LRM.Restricted_Null 

493 # lobster-trace: LRM.Null_Is_Invalid 

494 

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

496 if self.typ is None: 

497 mh.error(self.location, 

498 "null is not permitted here") 

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

500 mh.error(self.location, 

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

502 (typ.__name__, 

503 self.typ.__class__.__name__)) 

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

505 mh.error(self.location, 

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

507 (typ.name, 

508 self.typ.name)) 

509 

510 def resolve_references(self, mh): 

511 assert isinstance(mh, Message_Handler) 

512 

513 @abstractmethod 

514 def can_be_null(self): 

515 """Test if the expression could return null 

516 

517 Checks the expression if it could generate a null value 

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

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

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

521 occur earlier. 

522 

523 :return: possibility of encountering null 

524 :rtype: bool 

525 

526 """ 

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

528 self.__class__.__name__ 

529 

530 

531class Implicit_Null(Expression): 

532 """Synthesised null values 

533 

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

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

536 implicit null expression for this. 

537 

538 For example given this TRLC type:: 

539 

540 type T { 

541 x optional Integer 

542 } 

543 

544 And this declaration:: 

545 

546 T Potato {} 

547 

548 Then the field mapping for Potato will be:: 

549 

550 {x: Implicit_Null} 

551 

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

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

554 that can appear in check expressions. 

555 

556 """ 

557 def __init__(self, composite_object, composite_component): 

558 # lobster-trace: LRM.Unspecified_Optional_Components 

559 assert isinstance(composite_object, (Record_Object, 

560 Tuple_Aggregate)) 

561 assert isinstance(composite_component, Composite_Component) 

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

563 

564 def to_string(self): 

565 return "null" 

566 

567 def evaluate(self, mh, context): 

568 # lobster-trace: LRM.Unspecified_Optional_Components 

569 assert isinstance(mh, Message_Handler) 

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

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

572 

573 def to_python_object(self): 

574 return None 

575 

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

577 # lobster-exclude: Debugging feature 

578 self.write_indent(indent, "Implicit_Null") 

579 

580 def can_be_null(self): 

581 return True 

582 

583 

584class Literal(Expression, metaclass=ABCMeta): 

585 """Abstract base for all Literals 

586 

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

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

589 check if you are dealing with a literal:: 

590 

591 isinstance(my_expression, Literal) 

592 

593 """ 

594 @abstractmethod 

595 def to_python_object(self): 

596 assert False 

597 

598 

599class Null_Literal(Literal): 

600 # lobster-trace: LRM.Primary 

601 """The null literal 

602 

603 This can appear in check expressions:: 

604 

605 a /= null implies a > 5 

606 ^^^^ 

607 

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

609 values that appear in record objects. 

610 

611 """ 

612 def __init__(self, token): 

613 assert isinstance(token, Token) 

614 assert token.kind == "KEYWORD" 

615 assert token.value == "null" 

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

617 

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

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

620 

621 def to_string(self): 

622 return "null" 

623 

624 def evaluate(self, mh, context): 

625 assert isinstance(mh, Message_Handler) 

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

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

628 

629 def to_python_object(self): 

630 return None 

631 

632 def can_be_null(self): 

633 return True 

634 

635 

636class Integer_Literal(Literal): 

637 # lobster-trace: LRM.Integer_Values 

638 # lobster-trace: LRM.Primary 

639 """Integer literals 

640 

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

642 actually a unary negation expression, operating on a positive 

643 integer literal:: 

644 

645 x == -5 

646 

647 This would create the following tree:: 

648 

649 OP_EQUALITY 

650 NAME_REFERENCE x 

651 UNARY_EXPRESSION - 

652 INTEGER_LITERAL 5 

653 

654 :attribute value: the non-negative integer value 

655 :type: int 

656 """ 

657 def __init__(self, token, typ): 

658 assert isinstance(token, Token) 

659 assert token.kind == "INTEGER" 

660 assert isinstance(typ, Builtin_Integer) 

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

662 

663 self.value = token.value 

664 

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

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

667 

668 def to_string(self): 

669 return str(self.value) 

670 

671 def evaluate(self, mh, context): 

672 assert isinstance(mh, Message_Handler) 

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

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

675 

676 def to_python_object(self): 

677 return self.value 

678 

679 def can_be_null(self): 

680 return False 

681 

682 

683class Decimal_Literal(Literal): 

684 # lobster-trace: LRM.Decimal_Values 

685 # lobster-trace: LRM.Primary 

686 """Decimal literals 

687 

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

689 actually a unary negation expression, operating on a positive 

690 decimal literal:: 

691 

692 x == -5.0 

693 

694 This would create the following tree:: 

695 

696 OP_EQUALITY 

697 NAME_REFERENCE x 

698 UNARY_EXPRESSION - 

699 DECIMAL_LITERAL 5.0 

700 

701 :attribute value: the non-negative decimal value 

702 :type: fractions.Fraction 

703 """ 

704 def __init__(self, token, typ): 

705 assert isinstance(token, Token) 

706 assert token.kind == "DECIMAL" 

707 assert isinstance(typ, Builtin_Decimal) 

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

709 

710 self.value = token.value 

711 

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

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

714 

715 def to_string(self): 

716 return str(self.value) 

717 

718 def evaluate(self, mh, context): 

719 assert isinstance(mh, Message_Handler) 

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

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

722 

723 def to_python_object(self): 

724 return float(self.value) 

725 

726 def can_be_null(self): 

727 return False 

728 

729 

730class String_Literal(Literal): 

731 # lobster-trace: LRM.String_Values 

732 # lobster-trace: LRM.Markup_String_Values 

733 # lobster-trace: LRM.Primary 

734 """String literals 

735 

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

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

738 

739 "foo\\"bar" 

740 

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

742 

743 :attribute value: string content 

744 :type: str 

745 

746 :attribute references: resolved references of a markup string 

747 :type: list[Record_Reference] 

748 

749 """ 

750 def __init__(self, token, typ): 

751 assert isinstance(token, Token) 

752 assert token.kind == "STRING" 

753 assert isinstance(typ, Builtin_String) 

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

755 

756 self.value = token.value 

757 self.has_references = isinstance(typ, Builtin_Markup_String) 

758 self.references = [] 

759 

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

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

762 if self.has_references: 

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

764 for ref in self.references: 

765 ref.dump(indent + 2) 

766 

767 def to_string(self): 

768 return self.value 

769 

770 def evaluate(self, mh, context): 

771 assert isinstance(mh, Message_Handler) 

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

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

774 

775 def to_python_object(self): 

776 return self.value 

777 

778 def resolve_references(self, mh): 

779 assert isinstance(mh, Message_Handler) 

780 for ref in self.references: 

781 ref.resolve_references(mh) 

782 

783 def can_be_null(self): 

784 return False 

785 

786 

787class Boolean_Literal(Literal): 

788 # lobster-trace: LRM.Boolean_Values 

789 # lobster-trace: LRM.Primary 

790 """Boolean values 

791 

792 :attribute value: the boolean value 

793 :type: bool 

794 """ 

795 def __init__(self, token, typ): 

796 assert isinstance(token, Token) 

797 assert token.kind == "KEYWORD" 

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

799 assert isinstance(typ, Builtin_Boolean) 

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

801 

802 self.value = token.value == "true" 

803 

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

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

806 

807 def to_string(self): 

808 return str(self.value) 

809 

810 def evaluate(self, mh, context): 

811 assert isinstance(mh, Message_Handler) 

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

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

814 

815 def to_python_object(self): 

816 return self.value 

817 

818 def can_be_null(self): 

819 return False 

820 

821 

822class Enumeration_Literal(Literal): 

823 """Enumeration values 

824 

825 Note that this is distinct from 

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

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

828 

829 foo != my_enum.POTATO 

830 ^^^^^^^^^^^^^^ 

831 

832 To get to the string value of the enumeration literal 

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

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

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

836 ``enum_lit.value.n_typ.name``. 

837 

838 :attribute value: enumeration value 

839 :type: Enumeration_Literal_Spec 

840 

841 """ 

842 def __init__(self, location, literal): 

843 # lobster-exclude: Constructor only declares variables 

844 assert isinstance(literal, Enumeration_Literal_Spec) 

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

846 

847 self.value = literal 

848 

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

850 # lobster-exclude: Debugging feature 

851 self.write_indent(indent, 

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

853 

854 def to_string(self): 

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

856 

857 def evaluate(self, mh, context): 

858 assert isinstance(mh, Message_Handler) 

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

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

861 

862 def to_python_object(self): 

863 return self.value.name 

864 

865 def can_be_null(self): 

866 return False 

867 

868 

869class Array_Aggregate(Expression): 

870 """Instances of array types 

871 

872 This is created when assigning to array components:: 

873 

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

875 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 

876 

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

878 limited: 

879 

880 * :class:`Literal` 

881 * :class:`Array_Aggregate` 

882 * :class:`Record_Reference` 

883 

884 :attribute value: contents of the array 

885 :type: list[Expression] 

886 

887 """ 

888 def __init__(self, location, typ): 

889 # lobster-trace: LRM.Record_Object_Declaration 

890 

891 super().__init__(location, typ) 

892 self.value = [] 

893 

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

895 # lobster-exclude: Debugging feature 

896 self.write_indent(indent, "Array_Aggregate") 

897 for n_value in self.value: 

898 n_value.dump(indent + 1) 

899 

900 def append(self, value): 

901 assert isinstance(value, (Literal, 

902 Unary_Expression, 

903 Array_Aggregate, 

904 Tuple_Aggregate, 

905 Record_Reference)) 

906 self.value.append(value) 

907 

908 def to_string(self): 

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

910 

911 def evaluate(self, mh, context): 

912 assert isinstance(mh, Message_Handler) 

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

914 return Value(self.location, 

915 list(element.evaluate(mh, context) 

916 for element in self.value), 

917 self.typ) 

918 

919 def resolve_references(self, mh): 

920 assert isinstance(mh, Message_Handler) 

921 

922 for val in self.value: 

923 val.resolve_references(mh) 

924 

925 def to_python_object(self): 

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

927 

928 def can_be_null(self): 

929 return False 

930 

931 

932class Tuple_Aggregate(Expression): 

933 """Instances of a tuple 

934 

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

936 two forms, the ordinary form:: 

937 

938 coordinate = (12.3, 40.0) 

939 ^^^^^^^^^^^^ 

940 

941 And the separator form:: 

942 

943 item = 12345@42 

944 ^^^^^^^^ 

945 

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

947 syntactic sugar. 

948 

949 :attribute value: contents of the tuple 

950 :type: dict[str, Expression] 

951 

952 """ 

953 def __init__(self, location, typ): 

954 # lobster-trace: LRM.Unspecified_Optional_Components 

955 # lobster-trace: LRM.Record_Object_Declaration 

956 

957 super().__init__(location, typ) 

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

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

960 

961 def assign(self, field, value): 

962 assert isinstance(field, str) 

963 assert isinstance(value, (Literal, 

964 Unary_Expression, 

965 Tuple_Aggregate, 

966 Record_Reference)), \ 

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

968 assert field in self.typ.components 

969 

970 self.value[field] = value 

971 

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

973 # lobster-exclude: Debugging feature 

974 self.write_indent(indent, "Tuple_Aggregate") 

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

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

977 if isinstance(n_item, Composite_Component): 

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

979 

980 def to_string(self): 

981 first = True 

982 if self.typ.has_separators(): 

983 rv = "" 

984 else: 

985 rv = "(" 

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

987 if isinstance(n_item, Separator): 

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

989 elif first: 

990 first = False 

991 else: 

992 rv += ", " 

993 

994 if isinstance(n_item, Composite_Component): 

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

996 if self.typ.has_separators(): 

997 rv = "" 

998 else: 

999 rv = ")" 

1000 return rv 

1001 

1002 def evaluate(self, mh, context): 

1003 assert isinstance(mh, Message_Handler) 

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

1005 return Value(self.location, 

1006 {name : element.evaluate(mh, context) 

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

1008 self.typ) 

1009 

1010 def resolve_references(self, mh): 

1011 assert isinstance(mh, Message_Handler) 

1012 

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

1014 val.resolve_references(mh) 

1015 

1016 def to_python_object(self): 

1017 return {name: value.to_python_object() 

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

1019 

1020 def can_be_null(self): 

1021 return False 

1022 

1023 

1024class Record_Reference(Expression): 

1025 """Reference to another record object 

1026 

1027 This can appear in record object declarations:: 

1028 

1029 Requirement Kitten { 

1030 depends_on = Other_Package.Cat 

1031 ^1 ^2 

1032 } 

1033 

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

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

1036 the target attribute. 

1037 

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

1039 immediately resolved on parsing in the TRLC language. 

1040 

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

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

1043 in this AST node. 

1044 

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

1046 :type: str 

1047 

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

1049 :type: Record_Object 

1050 

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

1052 :type: Package 

1053 

1054 """ 

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

1056 # lobster-exclude: Constructor only declares variables 

1057 assert isinstance(location, Location) 

1058 assert isinstance(name, str) 

1059 assert isinstance(typ, Record_Type) or typ is None 

1060 assert isinstance(package, Package) 

1061 super().__init__(location, typ) 

1062 

1063 self.name = name 

1064 self.target = None 

1065 self.package = package 

1066 

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

1068 # lobster-exclude: Debugging feature 

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

1070 self.write_indent(indent + 1, 

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

1072 

1073 def to_string(self): 

1074 return self.name 

1075 

1076 def evaluate(self, mh, context): 

1077 assert isinstance(mh, Message_Handler) 

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

1079 return Value(self.location, self, self.typ) 

1080 

1081 def resolve_references(self, mh): 

1082 # lobster-trace: LRM.References_To_Extensions 

1083 assert isinstance(mh, Message_Handler) 

1084 

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

1086 mh = mh, 

1087 name = self.name, 

1088 error_location = self.location, 

1089 required_subclass = Record_Object) 

1090 if self.typ is None: 

1091 self.typ = self.target.n_typ 

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

1093 mh.error(self.location, 

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

1095 (self.typ.name, 

1096 self.target.name, 

1097 self.target.n_typ.name)) 

1098 

1099 def to_python_object(self): 

1100 return self.target.fully_qualified_name() 

1101 

1102 def can_be_null(self): 

1103 return False 

1104 

1105 

1106class Name_Reference(Expression): 

1107 # lobster-trace: LRM.Qualified_Name 

1108 # lobster-trace: LRM.Static_Regular_Expression 

1109 

1110 """Reference to a name 

1111 

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

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

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

1115 

1116 For example:: 

1117 

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

1119 ^1 ^2 

1120 

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

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

1123 :class:`Quantified_Variable`. 

1124 

1125 :attribute entity: the entity named here 

1126 :type: Composite_Component, Quantified_Variable 

1127 """ 

1128 def __init__(self, location, entity): 

1129 assert isinstance(entity, (Composite_Component, 

1130 Quantified_Variable)) 

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

1132 self.entity = entity 

1133 

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

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

1136 

1137 def to_string(self): 

1138 return self.entity.name 

1139 

1140 def evaluate(self, mh, context): 

1141 assert isinstance(mh, Message_Handler) 

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

1143 

1144 if context is None: 

1145 mh.error(self.location, 

1146 "cannot be used in a static context") 

1147 

1148 assert self.entity.name in context 

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

1150 

1151 def can_be_null(self): 

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

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

1154 # optional. 

1155 if isinstance(self.entity, Composite_Component): 

1156 return self.entity.optional 

1157 else: 

1158 return False 

1159 

1160 

1161class Unary_Expression(Expression): 

1162 """Expression with only one operand 

1163 

1164 This captures the following operations: 

1165 

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

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

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

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

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

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

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

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

1174 

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

1176 

1177 :attribute operator: the operation 

1178 :type: Unary_Operator 

1179 

1180 :attribute n_operand: the expression we operate on 

1181 :type: Expression 

1182 

1183 """ 

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

1185 # lobster-trace: LRM.Simple_Expression 

1186 # lobster-trace: LRM.Relation 

1187 # lobster-trace: LRM.Factor 

1188 # lobster-trace: LRM.Signature_Len 

1189 # lobster-trace: LRM.Signature_Type_Conversion 

1190 

1191 super().__init__(location, typ) 

1192 assert isinstance(mh, Message_Handler) 

1193 assert isinstance(operator, Unary_Operator) 

1194 assert isinstance(n_operand, Expression) 

1195 self.operator = operator 

1196 self.n_operand = n_operand 

1197 

1198 if operator in (Unary_Operator.MINUS, 

1199 Unary_Operator.PLUS, 

1200 Unary_Operator.ABSOLUTE_VALUE): 

1201 self.n_operand.ensure_type(mh, Builtin_Numeric_Type) 

1202 elif operator == Unary_Operator.LOGICAL_NOT: 

1203 self.n_operand.ensure_type(mh, Builtin_Boolean) 

1204 elif operator == Unary_Operator.STRING_LENGTH: 

1205 self.n_operand.ensure_type(mh, Builtin_String) 

1206 elif operator == Unary_Operator.ARRAY_LENGTH: 

1207 self.n_operand.ensure_type(mh, Array_Type) 

1208 elif operator == Unary_Operator.CONVERSION_TO_INT: 

1209 self.n_operand.ensure_type(mh, Builtin_Numeric_Type) 

1210 elif operator == Unary_Operator.CONVERSION_TO_DECIMAL: 

1211 self.n_operand.ensure_type(mh, Builtin_Numeric_Type) 

1212 else: 

1213 mh.ice_loc(self.location, 

1214 "unexpected unary operation %s" % operator) 

1215 

1216 def to_string(self): 

1217 prefix_operators = { 

1218 Unary_Operator.MINUS : "-", 

1219 Unary_Operator.PLUS : "+", 

1220 Unary_Operator.ABSOLUTE_VALUE : "abs ", 

1221 Unary_Operator.LOGICAL_NOT : "not ", 

1222 } 

1223 function_calls = { 

1224 Unary_Operator.STRING_LENGTH : "len", 

1225 Unary_Operator.ARRAY_LENGTH : "len", 

1226 Unary_Operator.CONVERSION_TO_INT : "Integer", 

1227 Unary_Operator.CONVERSION_TO_DECIMAL : "Decimal" 

1228 } 

1229 

1230 if self.operator in prefix_operators: 

1231 return prefix_operators[self.operator] + \ 

1232 self.n_operand.to_string() 

1233 

1234 elif self.operator in function_calls: 

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

1236 self.n_operand.to_string()) 

1237 

1238 else: 

1239 assert False 

1240 

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

1242 # lobster-exclude: Debugging feature 

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

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

1245 self.n_operand.dump(indent + 1) 

1246 

1247 def evaluate(self, mh, context): 

1248 # lobster-trace: LRM.Null_Is_Invalid 

1249 # lobster-trace: LRM.Signature_Len 

1250 # lobster-trace: LRM.Signature_Type_Conversion 

1251 # lobster-trace: LRM.Len_Semantics 

1252 # lobster-trace: LRM.Integer_Conversion_Semantics 

1253 # lobster-trace: LRM.Decimal_Conversion_Semantics 

1254 

1255 assert isinstance(mh, Message_Handler) 

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

1257 

1258 v_operand = self.n_operand.evaluate(mh, context) 

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

1260 mh.error(v_operand.location, 

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

1262 (self.to_string(), 

1263 mh.cross_file_reference(self.location))) 

1264 

1265 if self.operator == Unary_Operator.MINUS: 

1266 return Value(location = self.location, 

1267 value = -v_operand.value, 

1268 typ = self.typ) 

1269 elif self.operator == Unary_Operator.PLUS: 

1270 return Value(location = self.location, 

1271 value = +v_operand.value, 

1272 typ = self.typ) 

1273 elif self.operator == Unary_Operator.LOGICAL_NOT: 

1274 return Value(location = self.location, 

1275 value = not v_operand.value, 

1276 typ = self.typ) 

1277 elif self.operator == Unary_Operator.ABSOLUTE_VALUE: 

1278 return Value(location = self.location, 

1279 value = abs(v_operand.value), 

1280 typ = self.typ) 

1281 elif self.operator in (Unary_Operator.STRING_LENGTH, 

1282 Unary_Operator.ARRAY_LENGTH): 

1283 return Value(location = self.location, 

1284 value = len(v_operand.value), 

1285 typ = self.typ) 

1286 elif self.operator == Unary_Operator.CONVERSION_TO_INT: 

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

1288 return Value( 

1289 location = self.location, 

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

1291 typ = self.typ) 

1292 else: 

1293 return Value(location = self.location, 

1294 value = v_operand.value, 

1295 typ = self.typ) 

1296 elif self.operator == Unary_Operator.CONVERSION_TO_DECIMAL: 

1297 return Value(location = self.location, 

1298 value = Fraction(v_operand.value), 

1299 typ = self.typ) 

1300 else: 

1301 mh.ice_loc(self.location, 

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

1303 

1304 def to_python_object(self): 

1305 assert self.operator in (Unary_Operator.MINUS, 

1306 Unary_Operator.PLUS) 

1307 val = self.n_operand.to_python_object() 

1308 if self.operator == Unary_Operator.MINUS: 

1309 return -val 

1310 else: 

1311 return val 

1312 

1313 def can_be_null(self): 

1314 return False 

1315 

1316 

1317class Binary_Expression(Expression): 

1318 """Expression with two operands 

1319 

1320 This captures the following operations: 

1321 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1344 

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

1346 

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

1348 rationals and strings. 

1349 

1350 :attribute operator: the operation 

1351 :type: Binary_Operator 

1352 

1353 :attribute n_lhs: the first operand 

1354 :type: Expression 

1355 

1356 :attribute n_rhs: the second operand 

1357 :type: Expression 

1358 

1359 """ 

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

1361 # lobster-trace: LRM.Expression 

1362 # lobster-trace: LRM.Relation 

1363 # lobster-trace: LRM.Simple_Expression 

1364 # lobster-trace: LRM.Term 

1365 # lobster-trace: LRM.Factor 

1366 # lobster-trace: LRM.Signature_String_End_Functions 

1367 # lobster-trace: LRM.Signature_Matches 

1368 

1369 super().__init__(location, typ) 

1370 assert isinstance(mh, Message_Handler) 

1371 assert isinstance(operator, Binary_Operator) 

1372 assert isinstance(n_lhs, Expression) 

1373 assert isinstance(n_rhs, Expression) 

1374 self.operator = operator 

1375 self.n_lhs = n_lhs 

1376 self.n_rhs = n_rhs 

1377 

1378 if operator in (Binary_Operator.LOGICAL_AND, 

1379 Binary_Operator.LOGICAL_OR, 

1380 Binary_Operator.LOGICAL_XOR, 

1381 Binary_Operator.LOGICAL_IMPLIES): 

1382 self.n_lhs.ensure_type(mh, Builtin_Boolean) 

1383 self.n_rhs.ensure_type(mh, Builtin_Boolean) 

1384 

1385 elif operator in (Binary_Operator.COMP_EQ, 

1386 Binary_Operator.COMP_NEQ): 

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

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

1389 pass 

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

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

1392 # types match 

1393 mh.error(self.location, 

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

1395 (self.n_lhs.typ.name, 

1396 self.n_rhs.typ.name)) 

1397 

1398 elif operator in (Binary_Operator.COMP_LT, 

1399 Binary_Operator.COMP_LEQ, 

1400 Binary_Operator.COMP_GT, 

1401 Binary_Operator.COMP_GEQ): 

1402 self.n_lhs.ensure_type(mh, Builtin_Numeric_Type) 

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

1404 

1405 elif operator in (Binary_Operator.STRING_CONTAINS, 

1406 Binary_Operator.STRING_STARTSWITH, 

1407 Binary_Operator.STRING_ENDSWITH, 

1408 Binary_Operator.STRING_REGEX): 

1409 self.n_lhs.ensure_type(mh, Builtin_String) 

1410 self.n_rhs.ensure_type(mh, Builtin_String) 

1411 

1412 elif operator == Binary_Operator.ARRAY_CONTAINS: 

1413 self.n_rhs.ensure_type(mh, Array_Type) 

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

1415 

1416 elif operator == Binary_Operator.PLUS: 

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

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

1419 else: 

1420 self.n_lhs.ensure_type(mh, Builtin_String) 

1421 self.n_rhs.ensure_type(mh, Builtin_String) 

1422 

1423 elif operator in (Binary_Operator.MINUS, 

1424 Binary_Operator.TIMES, 

1425 Binary_Operator.DIVIDE): 

1426 self.n_lhs.ensure_type(mh, Builtin_Numeric_Type) 

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

1428 

1429 elif operator == Binary_Operator.POWER: 

1430 self.n_lhs.ensure_type(mh, Builtin_Numeric_Type) 

1431 self.n_rhs.ensure_type(mh, Builtin_Integer) 

1432 

1433 elif operator == Binary_Operator.REMAINDER: 

1434 self.n_lhs.ensure_type(mh, Builtin_Integer) 

1435 self.n_rhs.ensure_type(mh, Builtin_Integer) 

1436 

1437 elif operator == Binary_Operator.INDEX: 

1438 self.n_lhs.ensure_type(mh, Array_Type) 

1439 self.n_rhs.ensure_type(mh, Builtin_Integer) 

1440 

1441 else: 

1442 mh.ice_loc(self.location, 

1443 "unexpected binary operation %s" % operator) 

1444 

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

1446 # lobster-exclude: Debugging feature 

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

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

1449 self.n_lhs.dump(indent + 1) 

1450 self.n_rhs.dump(indent + 1) 

1451 

1452 def to_string(self): 

1453 infix_operators = { 

1454 Binary_Operator.LOGICAL_AND : "and", 

1455 Binary_Operator.LOGICAL_OR : "or", 

1456 Binary_Operator.LOGICAL_XOR : "xor", 

1457 Binary_Operator.LOGICAL_IMPLIES : "implies", 

1458 Binary_Operator.COMP_EQ : "==", 

1459 Binary_Operator.COMP_NEQ : "!=", 

1460 Binary_Operator.COMP_LT : "<", 

1461 Binary_Operator.COMP_LEQ : "<=", 

1462 Binary_Operator.COMP_GT : ">", 

1463 Binary_Operator.COMP_GEQ : ">=", 

1464 Binary_Operator.STRING_CONTAINS : "in", 

1465 Binary_Operator.ARRAY_CONTAINS : "in", 

1466 Binary_Operator.PLUS : "+", 

1467 Binary_Operator.MINUS : "-", 

1468 Binary_Operator.TIMES : "*", 

1469 Binary_Operator.DIVIDE : "/", 

1470 Binary_Operator.REMAINDER : "%", 

1471 Binary_Operator.POWER : "**", 

1472 } 

1473 string_functions = { 

1474 Binary_Operator.STRING_STARTSWITH : "startswith", 

1475 Binary_Operator.STRING_ENDSWITH : "endswith", 

1476 Binary_Operator.STRING_REGEX : "matches", 

1477 } 

1478 

1479 if self.operator in infix_operators: 

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

1481 infix_operators[self.operator], 

1482 self.n_rhs.to_string()) 

1483 

1484 elif self.operator in string_functions: 

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

1486 self.n_lhs.to_string(), 

1487 self.n_rhs.to_string()) 

1488 

1489 elif self.operator == Binary_Operator.INDEX: 

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

1491 self.n_rhs.to_string()) 

1492 

1493 else: 

1494 assert False 

1495 

1496 def evaluate(self, mh, context): 

1497 # lobster-trace: LRM.Null_Equivalence 

1498 # lobster-trace: LRM.Null_Is_Invalid 

1499 # lobster-trace: LRM.Signature_String_End_Functions 

1500 # lobster-trace: LRM.Signature_Matches 

1501 # lobster-trace: LRM.Startswith_Semantics 

1502 # lobster-trace: LRM.Endswith_Semantics 

1503 # lobster-trace: LRM.Matches_Semantics 

1504 

1505 assert isinstance(mh, Message_Handler) 

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

1507 

1508 v_lhs = self.n_lhs.evaluate(mh, context) 

1509 if v_lhs.value is None and \ 

1510 self.operator not in (Binary_Operator.COMP_EQ, 

1511 Binary_Operator.COMP_NEQ): 

1512 mh.error(v_lhs.location, 

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

1514 (self.to_string(), 

1515 mh.cross_file_reference(self.location))) 

1516 

1517 # Check for the short-circuit operators first 

1518 if self.operator == Binary_Operator.LOGICAL_AND: 

1519 assert isinstance(v_lhs.value, bool) 

1520 if v_lhs.value: 

1521 return self.n_rhs.evaluate(mh, context) 

1522 else: 

1523 return v_lhs 

1524 

1525 elif self.operator == Binary_Operator.LOGICAL_OR: 

1526 assert isinstance(v_lhs.value, bool) 

1527 if v_lhs.value: 

1528 return v_lhs 

1529 else: 

1530 return self.n_rhs.evaluate(mh, context) 

1531 

1532 elif self.operator == Binary_Operator.LOGICAL_IMPLIES: 

1533 assert isinstance(v_lhs.value, bool) 

1534 if v_lhs.value: 

1535 return self.n_rhs.evaluate(mh, context) 

1536 else: 

1537 return Value(location = self.location, 

1538 value = True, 

1539 typ = self.typ) 

1540 

1541 # Otherwise, evaluate RHS and do the operation 

1542 v_rhs = self.n_rhs.evaluate(mh, context) 

1543 if v_rhs.value is None and \ 

1544 self.operator not in (Binary_Operator.COMP_EQ, 

1545 Binary_Operator.COMP_NEQ): 

1546 mh.error(v_rhs.location, 

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

1548 (self.to_string(), 

1549 mh.cross_file_reference(self.location))) 

1550 

1551 if self.operator == Binary_Operator.LOGICAL_XOR: 

1552 assert isinstance(v_lhs.value, bool) 

1553 assert isinstance(v_rhs.value, bool) 

1554 return Value(location = self.location, 

1555 value = v_lhs.value ^ v_rhs.value, 

1556 typ = self.typ) 

1557 

1558 elif self.operator == Binary_Operator.COMP_EQ: 

1559 return Value(location = self.location, 

1560 value = v_lhs.value == v_rhs.value, 

1561 typ = self.typ) 

1562 

1563 elif self.operator == Binary_Operator.COMP_NEQ: 

1564 return Value(location = self.location, 

1565 value = v_lhs.value != v_rhs.value, 

1566 typ = self.typ) 

1567 

1568 elif self.operator in (Binary_Operator.COMP_LT, 

1569 Binary_Operator.COMP_LEQ, 

1570 Binary_Operator.COMP_GT, 

1571 Binary_Operator.COMP_GEQ): 

1572 return Value( 

1573 location = self.location, 

1574 value = { 

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

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

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

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

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

1580 typ = self.typ) 

1581 

1582 elif self.operator == Binary_Operator.STRING_CONTAINS: 

1583 assert isinstance(v_lhs.value, str) 

1584 assert isinstance(v_rhs.value, str) 

1585 

1586 return Value(location = self.location, 

1587 value = v_lhs.value in v_rhs.value, 

1588 typ = self.typ) 

1589 

1590 elif self.operator == Binary_Operator.STRING_STARTSWITH: 

1591 assert isinstance(v_lhs.value, str) 

1592 assert isinstance(v_rhs.value, str) 

1593 return Value(location = self.location, 

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

1595 typ = self.typ) 

1596 

1597 elif self.operator == Binary_Operator.STRING_ENDSWITH: 

1598 assert isinstance(v_lhs.value, str) 

1599 assert isinstance(v_rhs.value, str) 

1600 return Value(location = self.location, 

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

1602 typ = self.typ) 

1603 

1604 elif self.operator == Binary_Operator.STRING_REGEX: 

1605 assert isinstance(v_lhs.value, str) 

1606 assert isinstance(v_rhs.value, str) 

1607 return Value(location = self.location, 

1608 value = re.match(v_rhs.value, 

1609 v_lhs.value) is not None, 

1610 typ = self.typ) 

1611 

1612 elif self.operator == Binary_Operator.ARRAY_CONTAINS: 

1613 assert isinstance(v_rhs.value, list) 

1614 

1615 return Value(location = self.location, 

1616 value = v_lhs in v_rhs.value, 

1617 typ = self.typ) 

1618 

1619 elif self.operator == Binary_Operator.PLUS: 

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

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

1622 return Value(location = self.location, 

1623 value = v_lhs.value + v_rhs.value, 

1624 typ = self.typ) 

1625 

1626 elif self.operator == Binary_Operator.MINUS: 

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

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

1629 return Value(location = self.location, 

1630 value = v_lhs.value - v_rhs.value, 

1631 typ = self.typ) 

1632 

1633 elif self.operator == Binary_Operator.TIMES: 

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

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

1636 return Value(location = self.location, 

1637 value = v_lhs.value * v_rhs.value, 

1638 typ = self.typ) 

1639 

1640 elif self.operator == Binary_Operator.DIVIDE: 

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

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

1643 

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

1645 mh.error(v_rhs.location, 

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

1647 (self.to_string(), 

1648 mh.cross_file_reference(self.location))) 

1649 

1650 if isinstance(v_lhs.value, int): 

1651 return Value(location = self.location, 

1652 value = v_lhs.value // v_rhs.value, 

1653 typ = self.typ) 

1654 else: 

1655 return Value(location = self.location, 

1656 value = v_lhs.value / v_rhs.value, 

1657 typ = self.typ) 

1658 

1659 elif self.operator == Binary_Operator.REMAINDER: 

1660 assert isinstance(v_lhs.value, int) 

1661 assert isinstance(v_rhs.value, int) 

1662 

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

1664 mh.error(v_rhs.location, 

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

1666 (self.to_string(), 

1667 mh.cross_file_reference(self.location))) 

1668 

1669 return Value(location = self.location, 

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

1671 typ = self.typ) 

1672 

1673 elif self.operator == Binary_Operator.POWER: 

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

1675 assert isinstance(v_rhs.value, int) 

1676 return Value(location = self.location, 

1677 value = v_lhs.value ** v_rhs.value, 

1678 typ = self.typ) 

1679 

1680 elif self.operator == Binary_Operator.INDEX: 

1681 assert isinstance(v_lhs.value, list) 

1682 assert isinstance(v_rhs.value, int) 

1683 

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

1685 mh.error(v_rhs.location, 

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

1687 (self.to_string(), 

1688 mh.cross_file_reference(self.location))) 

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

1690 v_rhs.value > v_lhs.typ.upper_bound: 

1691 mh.error(v_rhs.location, 

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

1693 (v_lhs.typ.upper_bound, 

1694 self.to_string(), 

1695 mh.cross_file_reference(self.location))) 

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

1697 mh.error(v_lhs.location, 

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

1699 (self.to_string(), 

1700 mh.cross_file_reference(self.location))) 

1701 

1702 return Value(location = self.location, 

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

1704 typ = self.typ) 

1705 

1706 else: 

1707 mh.ice_loc(self.location, 

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

1709 

1710 def can_be_null(self): 

1711 return False 

1712 

1713 

1714class Field_Access_Expression(Expression): 

1715 """Tuple field access 

1716 

1717 For example in:: 

1718 

1719 foo.bar 

1720 ^1 ^2 

1721 

1722 :attribute n_prefix: expression with tuple type (see 1) 

1723 :type: Expression 

1724 

1725 :attribute n_field: a tuple field to dereference (see 2) 

1726 :type: Composite_Component 

1727 

1728 """ 

1729 def __init__(self, mh, location, n_prefix, n_field): 

1730 assert isinstance(mh, Message_Handler) 

1731 assert isinstance(n_prefix, Expression) 

1732 assert isinstance(n_field, Composite_Component) 

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

1734 self.n_prefix = n_prefix 

1735 self.n_field = n_field 

1736 

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

1738 

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

1740 # lobster-exclude: Debugging feature 

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

1742 self.n_prefix.dump(indent + 1) 

1743 

1744 def to_string(self): 

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

1746 

1747 def evaluate(self, mh, context): 

1748 assert isinstance(mh, Message_Handler) 

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

1750 

1751 return self.n_prefix.evaluate(mh, context).value[self.n_field.name] 

1752 

1753 def can_be_null(self): 

1754 return False 

1755 

1756 

1757class Range_Test(Expression): 

1758 """Range membership test 

1759 

1760 For example in:: 

1761 

1762 x in 1 .. field+1 

1763 ^lhs ^lower ^^^^^^^upper 

1764 

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

1766 you can have arbitrarily complex expressions here. 

1767 

1768 :attribute n_lhs: the expression to test 

1769 :type: Expression 

1770 

1771 :attribute n_lower: the lower bound 

1772 :type: Expression 

1773 

1774 :attribute n_upper: the upper bound 

1775 :type: Expression 

1776 

1777 """ 

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

1779 # lobster-trace: LRM.Relation 

1780 super().__init__(location, typ) 

1781 assert isinstance(mh, Message_Handler) 

1782 assert isinstance(n_lhs, Expression) 

1783 assert isinstance(n_lower, Expression) 

1784 assert isinstance(n_upper, Expression) 

1785 self.n_lhs = n_lhs 

1786 self.n_lower = n_lower 

1787 self.n_upper = n_upper 

1788 

1789 self.n_lhs.ensure_type(mh, Builtin_Numeric_Type) 

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

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

1792 

1793 def to_string(self): 

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

1795 self.n_lower.to_string(), 

1796 self.n_upper.to_string()) 

1797 

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

1799 # lobster-exclude: Debugging feature 

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

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

1802 self.n_lhs.dump(indent + 1) 

1803 self.n_lower.dump(indent + 1) 

1804 self.n_upper.dump(indent + 1) 

1805 

1806 def evaluate(self, mh, context): 

1807 # lobster-trace: LRM.Null_Is_Invalid 

1808 assert isinstance(mh, Message_Handler) 

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

1810 

1811 v_lhs = self.n_lhs.evaluate(mh, context) 

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

1813 mh.error(v_lhs.location, 

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

1815 (self.to_string(), 

1816 mh.cross_file_reference(self.location))) 

1817 

1818 v_lower = self.n_lower.evaluate(mh, context) 

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

1820 mh.error(v_lower.location, 

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

1822 (self.to_string(), 

1823 mh.cross_file_reference(self.location))) 

1824 

1825 v_upper = self.n_upper.evaluate(mh, context) 

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

1827 mh.error(v_upper.location, 

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

1829 (self.to_string(), 

1830 mh.cross_file_reference(self.location))) 

1831 

1832 return Value(location = self.location, 

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

1834 typ = self.typ) 

1835 

1836 def can_be_null(self): 

1837 return False 

1838 

1839 

1840class OneOf_Expression(Expression): 

1841 """OneOf expression 

1842 

1843 For example in:: 

1844 

1845 oneof(a, b, c) 

1846 ^^^^^^^ choices 

1847 

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

1849 :type: list[Expression] 

1850 """ 

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

1852 # lobster-trace: LRM.Signature_OneOf 

1853 super().__init__(location, typ) 

1854 assert isinstance(typ, Builtin_Boolean) 

1855 assert isinstance(mh, Message_Handler) 

1856 assert isinstance(choices, list) 

1857 assert all(isinstance(item, Expression) 

1858 for item in choices) 

1859 self.choices = choices 

1860 

1861 for n_choice in choices: 

1862 n_choice.ensure_type(mh, Builtin_Boolean) 

1863 

1864 def to_string(self): 

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

1866 for n_choice in self.choices) 

1867 

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

1869 # lobster-exclude: Debugging feature 

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

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

1872 for n_choice in self.choices: 

1873 n_choice.dump(indent + 1) 

1874 

1875 def evaluate(self, mh, context): 

1876 # lobster-trace: LRM.OneOf_Semantics 

1877 assert isinstance(mh, Message_Handler) 

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

1879 

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

1881 for n_choice in self.choices] 

1882 

1883 return Value(location = self.location, 

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

1885 typ = self.typ) 

1886 

1887 def can_be_null(self): 

1888 return False 

1889 

1890 

1891class Action(Node): 

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

1893 

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

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

1896 Actions:: 

1897 

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

1899 ^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^ 

1900 

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

1902 the :class:`Conditional_Expression` itself. 

1903 

1904 :attribute kind: Either if or elseif 

1905 :type: str 

1906 

1907 :attribute n_cond: The boolean condition expression 

1908 :type: Expression 

1909 

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

1911 :type: Expression 

1912 

1913 """ 

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

1915 # lobster-trace: LRM.Conditional_Expression 

1916 assert isinstance(mh, Message_Handler) 

1917 assert isinstance(t_kind, Token) 

1918 assert t_kind.kind == "KEYWORD" 

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

1920 assert isinstance(n_condition, Expression) 

1921 assert isinstance(n_expression, Expression) 

1922 super().__init__(t_kind.location) 

1923 self.kind = t_kind.value 

1924 self.n_cond = n_condition 

1925 self.n_expr = n_expression 

1926 # lobster-trace: LRM.Conditional_Expression_Types 

1927 self.n_cond.ensure_type(mh, Builtin_Boolean) 

1928 

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

1930 # lobster-exclude: Debugging feature 

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

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

1933 self.n_cond.dump(indent + 2) 

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

1935 self.n_expr.dump(indent + 2) 

1936 

1937 def to_string(self): 

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

1939 self.n_cond.to_string(), 

1940 self.n_expr.to_string()) 

1941 

1942 

1943class Conditional_Expression(Expression): 

1944 """A conditional expression 

1945 

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

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

1948 expression with two Actions:: 

1949 

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

1951 ^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^ 

1952 

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

1954 

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

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

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

1958 of all actions and the else expression will match. 

1959 

1960 :attribute actions: a list of Actions 

1961 :type: list[Action] 

1962 

1963 :attribute else_expr: the else expression 

1964 :type: Expression 

1965 

1966 """ 

1967 def __init__(self, location, if_action): 

1968 # lobster-trace: LRM.Conditional_Expression 

1969 assert isinstance(if_action, Action) 

1970 assert if_action.kind == "if" 

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

1972 self.actions = [if_action] 

1973 self.else_expr = None 

1974 

1975 def add_elsif(self, mh, n_action): 

1976 # lobster-trace: LRM.Conditional_Expression 

1977 # lobster-trace; LRM.Conditional_Expression_Types 

1978 assert isinstance(mh, Message_Handler) 

1979 assert isinstance(n_action, Action) 

1980 assert n_action.kind == "elsif" 

1981 

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

1983 self.actions.append(n_action) 

1984 

1985 def set_else_part(self, mh, n_expr): 

1986 # lobster-trace: LRM.Conditional_Expression 

1987 # lobster-trace; LRM.Conditional_Expression_Types 

1988 assert isinstance(mh, Message_Handler) 

1989 assert isinstance(n_expr, Expression) 

1990 

1991 n_expr.ensure_type(mh, self.typ) 

1992 self.else_expr = n_expr 

1993 

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

1995 # lobster-exclude: Debugging feature 

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

1997 for action in self.actions: 

1998 action.dump(indent + 1) 

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

2000 self.else_expr.dump(indent + 2) 

2001 

2002 def to_string(self): 

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

2004 for action in self.actions) 

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

2006 rv += ")" 

2007 return rv 

2008 

2009 def evaluate(self, mh, context): 

2010 # lobster-trace: LRM.Conditional_Expression_Else 

2011 # lobster-trace: LRM.Conditional_Expression_Evaluation 

2012 # lobster-trace: LRM.Null_Is_Invalid 

2013 assert isinstance(mh, Message_Handler) 

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

2015 

2016 for action in self.actions: 

2017 v_cond = action.n_cond.evaluate(mh, context) 

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

2019 mh.error(v_cond.location, 

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

2021 (action.to_string(), 

2022 mh.cross_file_reference(self.location))) 

2023 if v_cond.value: 

2024 return action.n_expr.evaluate(mh, context) 

2025 

2026 return self.else_expr.evaluate(mh, context) 

2027 

2028 def can_be_null(self): 

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

2030 return True 

2031 

2032 return any(action.n_expr.can_be_null() 

2033 for action in self.actions) 

2034 

2035 

2036class Quantified_Expression(Expression): 

2037 """A quantified expression 

2038 

2039 For example:: 

2040 

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

2042 ^4 ^1 ^2 ^^^^^3 

2043 

2044 A quantified expression introduces and binds a 

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

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

2047 each component of the source in turn. 

2048 

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

2050 :type: Quantified_Variable 

2051 

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

2053 :type: Name_Reference 

2054 

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

2056 :type: Expression 

2057 

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

2059 :type: Boolean 

2060 

2061 """ 

2062 def __init__(self, mh, location, 

2063 typ, 

2064 universal, 

2065 n_variable, 

2066 n_source, 

2067 n_expr): 

2068 # lobster-trace: LRM.Quantified_Expression 

2069 # lobster-trace: LRM.Quantification_Type 

2070 super().__init__(location, typ) 

2071 assert isinstance(typ, Builtin_Boolean) 

2072 assert isinstance(universal, bool) 

2073 assert isinstance(n_variable, Quantified_Variable) 

2074 assert isinstance(n_expr, Expression) 

2075 assert isinstance(n_source, Name_Reference) 

2076 self.universal = universal 

2077 self.n_var = n_variable 

2078 self.n_expr = n_expr 

2079 self.n_source = n_source 

2080 self.n_expr.ensure_type(mh, Builtin_Boolean) 

2081 

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

2083 # lobster-exclude: Debugging feature 

2084 if self.universal: 

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

2086 else: 

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

2088 self.n_var.dump(indent + 1) 

2089 self.n_expr.dump(indent + 1) 

2090 

2091 def to_string(self): 

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

2093 if self.universal 

2094 else "exists", 

2095 self.n_var.name, 

2096 self.n_source.to_string(), 

2097 self.n_expr.to_string()) 

2098 

2099 def evaluate(self, mh, context): 

2100 # lobster-trace: LRM.Null_Is_Invalid 

2101 # lobster-trace: LRM.Universal_Quantification_Semantics 

2102 # lobster-trace: LRM.Existential_Quantification_Semantics 

2103 assert isinstance(mh, Message_Handler) 

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

2105 

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

2107 new_ctx = {} 

2108 else: 

2109 new_ctx = copy(context) 

2110 

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

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

2113 # error messages. 

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

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

2116 if isinstance(array_values, Implicit_Null): 

2117 mh.error(array_values.location, 

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

2119 "must not be null" % 

2120 (self.n_source.to_string(), 

2121 self.to_string(), 

2122 mh.cross_file_reference(self.location))) 

2123 else: 

2124 assert isinstance(array_values, Array_Aggregate) 

2125 

2126 rv = self.universal 

2127 loc = self.location 

2128 for binding in array_values.value: 

2129 new_ctx[self.n_var.name] = binding 

2130 result = self.n_expr.evaluate(mh, new_ctx) 

2131 assert isinstance(result.value, bool) 

2132 if self.universal and not result.value: 

2133 rv = False 

2134 loc = binding.location 

2135 break 

2136 elif not self.universal and result.value: 

2137 rv = True 

2138 loc = binding.location 

2139 break 

2140 

2141 return Value(location = loc, 

2142 value = rv, 

2143 typ = self.typ) 

2144 

2145 def can_be_null(self): 

2146 return False 

2147 

2148 

2149############################################################################## 

2150# AST Nodes (Entities) 

2151############################################################################## 

2152 

2153class Entity(Node, metaclass=ABCMeta): 

2154 """Base class for all entities. 

2155 

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

2157 allocate memory. Examples of entities are types and record 

2158 objects. 

2159 

2160 :attribute name: unqualified name of the entity 

2161 :type: str 

2162 

2163 """ 

2164 def __init__(self, name, location): 

2165 # lobster-trace: LRM.Described_Name_Equality 

2166 super().__init__(location) 

2167 assert isinstance(name, str) 

2168 self.name = name 

2169 

2170 

2171class Typed_Entity(Entity, metaclass=ABCMeta): 

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

2173 

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

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

2176 are record objects and components. 

2177 

2178 :attribute n_typ: type of the entity 

2179 :type: Type 

2180 

2181 """ 

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

2183 # lobster-exclude: Constructor only declares variables 

2184 super().__init__(name, location) 

2185 assert isinstance(n_typ, Type) 

2186 self.n_typ = n_typ 

2187 

2188 

2189class Quantified_Variable(Typed_Entity): 

2190 """Variable used in quantified expression. 

2191 

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

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

2194 

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

2196 ^ 

2197 

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

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

2200 

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

2202 :type: Type 

2203 

2204 """ 

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

2206 # lobster-exclude: Debugging feature 

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

2208 self.n_typ.dump(indent + 1) 

2209 

2210 

2211class Type(Entity, metaclass=ABCMeta): 

2212 """Abstract base class for all types. 

2213 

2214 """ 

2215 def perform_type_checks(self, mh, value): 

2216 assert isinstance(mh, Message_Handler) 

2217 assert isinstance(value, Expression) 

2218 return True 

2219 

2220 def get_example_value(self): 

2221 # lobster-exclude: utility method 

2222 assert False 

2223 

2224 

2225class Concrete_Type(Type, metaclass=ABCMeta): 

2226 # lobster-trace: LRM.Type_Declarations 

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

2228 

2229 :attribute n_package: package where this type was declared 

2230 :type: Package 

2231 """ 

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

2233 super().__init__(name, location) 

2234 assert isinstance(n_package, Package) 

2235 self.n_package = n_package 

2236 

2237 def fully_qualified_name(self): 

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

2239 

2240 :returns: the type's full name 

2241 :rtype: str 

2242 """ 

2243 return self.n_package.name + "." + self.name 

2244 

2245 def __hash__(self): 

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

2247 

2248 def __repr__(self): 

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

2250 self.fully_qualified_name()) 

2251 

2252 

2253class Builtin_Type(Type, metaclass=ABCMeta): 

2254 # lobster-trace: LRM.Builtin_Types 

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

2256 

2257 """ 

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

2259 

2260 def __init__(self, name): 

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

2262 

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

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

2265 

2266 

2267class Builtin_Numeric_Type(Builtin_Type, metaclass=ABCMeta): 

2268 # lobster-trace: LRM.Builtin_Types 

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

2270 

2271 """ 

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

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

2274 

2275 

2276class Builtin_Function(Entity): 

2277 # lobster-trace: LRM.Builtin_Functions 

2278 """Builtin functions. 

2279 

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

2281 

2282 :attribute arity: number of parameters 

2283 :type: int 

2284 

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

2286 :type: bool 

2287 

2288 """ 

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

2290 

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

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

2293 assert isinstance(arity, int) 

2294 assert isinstance(arity_at_least, bool) 

2295 assert arity >= 0 

2296 self.arity = arity 

2297 self.arity_at_least = arity_at_least 

2298 

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

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

2301 

2302 

2303class Array_Type(Type): 

2304 """Anonymous array type. 

2305 

2306 These are declared implicitly for each record component that has 

2307 an array specifier:: 

2308 

2309 foo Integer [5 .. *] 

2310 ^ 

2311 

2312 :attribute lower_bound: minimum number of elements 

2313 :type: int 

2314 

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

2316 :type: Location 

2317 

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

2319 :type: int 

2320 

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

2322 :type: Location 

2323 

2324 :attribute element_type: type of the array elements 

2325 :type: Type 

2326 

2327 """ 

2328 def __init__(self, 

2329 location, 

2330 element_type, 

2331 loc_lower, 

2332 lower_bound, 

2333 loc_upper, 

2334 upper_bound): 

2335 # lobster-exclude: Constructor only declares variables 

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

2337 assert isinstance(lower_bound, int) 

2338 assert lower_bound >= 0 

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

2340 assert upper_bound is None or upper_bound >= 0 

2341 assert isinstance(loc_lower, Location) 

2342 assert isinstance(loc_upper, Location) 

2343 

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

2345 name = "universal array" 

2346 elif upper_bound is None: 

2347 if lower_bound == 0: 

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

2349 else: 

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

2351 element_type.name) 

2352 elif lower_bound == upper_bound: 

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

2354 element_type.name) 

2355 else: 

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

2357 upper_bound, 

2358 element_type.name) 

2359 super().__init__(name, location) 

2360 self.lower_bound = lower_bound 

2361 self.loc_lower = loc_lower 

2362 self.upper_bound = upper_bound 

2363 self.loc_upper = loc_upper 

2364 self.element_type = element_type 

2365 

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

2367 # lobster-exclude: Debugging feature 

2368 self.write_indent(indent, "Array_Type") 

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

2370 if self.upper_bound is None: 

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

2372 else: 

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

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

2375 

2376 def perform_type_checks(self, mh, value): 

2377 assert isinstance(mh, Message_Handler) 

2378 if isinstance(value, Array_Aggregate): 

2379 return all(self.element_type.perform_type_checks(mh, v) 

2380 for v in value.value) 

2381 else: 

2382 assert isinstance(value, Implicit_Null) 

2383 return True 

2384 

2385 def get_example_value(self): 

2386 # lobster-exclude: utility method 

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

2388 

2389 

2390class Builtin_Integer(Builtin_Numeric_Type): 

2391 # lobster-trace: LRM.Builtin_Types 

2392 # lobster-trace: LRM.Integer_Values 

2393 """Builtin integer type.""" 

2394 def __init__(self): 

2395 super().__init__("Integer") 

2396 

2397 def get_example_value(self): 

2398 # lobster-exclude: utility method 

2399 return "100" 

2400 

2401 

2402class Builtin_Decimal(Builtin_Numeric_Type): 

2403 # lobster-trace: LRM.Builtin_Types 

2404 # lobster-trace: LRM.Decimal_Values 

2405 """Builtin decimal type.""" 

2406 def __init__(self): 

2407 super().__init__("Decimal") 

2408 

2409 def get_example_value(self): 

2410 # lobster-exclude: utility method 

2411 return "3.14" 

2412 

2413 

2414class Builtin_Boolean(Builtin_Type): 

2415 # lobster-trace: LRM.Builtin_Types 

2416 # lobster-trace: LRM.Boolean_Values 

2417 """Builtin boolean type.""" 

2418 def __init__(self): 

2419 super().__init__("Boolean") 

2420 

2421 def get_example_value(self): 

2422 # lobster-exclude: utility method 

2423 return "true" 

2424 

2425 

2426class Builtin_String(Builtin_Type): 

2427 # lobster-trace: LRM.Builtin_Types 

2428 # lobster-trace: LRM.String_Values 

2429 """Builtin string type.""" 

2430 def __init__(self): 

2431 super().__init__("String") 

2432 

2433 def get_example_value(self): 

2434 # lobster-exclude: utility method 

2435 return "\"potato\"" 

2436 

2437 

2438class Builtin_Markup_String(Builtin_String): 

2439 # lobster-trace: LRM.Builtin_Types 

2440 # lobster-trace: LRM.Markup_String_Values 

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

2442 objects. 

2443 """ 

2444 def __init__(self): 

2445 super().__init__() 

2446 self.name = "Markup_String" 

2447 

2448 def get_example_value(self): 

2449 # lobster-exclude: utility method 

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

2451 

2452 

2453class Package(Entity): 

2454 """Packages. 

2455 

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

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

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

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

2460 

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

2462 trlc file 

2463 :type: bool 

2464 

2465 :attribute symbols: symbol table of the package 

2466 :type: Symbol_Table 

2467 

2468 """ 

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

2470 # lobster-exclude: Constructor only declares variables 

2471 super().__init__(name, location) 

2472 assert isinstance(builtin_stab, Symbol_Table) 

2473 assert isinstance(declared_late, bool) 

2474 self.symbols = Symbol_Table() 

2475 self.symbols.make_visible(builtin_stab) 

2476 self.declared_late = declared_late 

2477 

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

2479 # lobster-exclude: Debugging feature 

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

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

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

2483 

2484 def __repr__(self): 

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

2486 self.name) 

2487 

2488 

2489class Composite_Type(Concrete_Type, metaclass=ABCMeta): 

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

2491 functionality. 

2492 

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

2494 :type: Symbol_Table[Composite_Component] 

2495 

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

2497 :type: str 

2498 

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

2500 inherited checks) 

2501 :type: list[Check] 

2502 

2503 """ 

2504 def __init__(self, 

2505 name, 

2506 description, 

2507 location, 

2508 package, 

2509 inherited_symbols=None): 

2510 # lobster-trace: LRM.Described_Name_Description 

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

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

2513 assert isinstance(inherited_symbols, Symbol_Table) or \ 

2514 inherited_symbols is None 

2515 

2516 self.components = Symbol_Table(inherited_symbols) 

2517 self.description = description 

2518 self.checks = [] 

2519 

2520 def add_check(self, n_check): 

2521 # lobster-trace: LRM.Check_Evaluation_Order 

2522 assert isinstance(n_check, Check) 

2523 self.checks.append(n_check) 

2524 

2525 def iter_checks(self): 

2526 # lobster-trace: LRM.Check_Evaluation_Order 

2527 yield from self.checks 

2528 

2529 def all_components(self): 

2530 # lobster-exclude: Convenience function 

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

2532 

2533 :rtype: list[Composite_Component] 

2534 """ 

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

2536 

2537 

2538class Composite_Component(Typed_Entity): 

2539 """Component in a record or tuple. 

2540 

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

2542 declared:: 

2543 

2544 type|tuple T { 

2545 foo "blah" optional Boolean 

2546 ^1 ^2 ^3 ^4 

2547 

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

2549 :type: str 

2550 

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

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

2553 :type: Composite_Type 

2554 

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

2556 :type: bool 

2557 

2558 """ 

2559 

2560 def __init__(self, 

2561 name, 

2562 description, 

2563 location, 

2564 member_of, 

2565 n_typ, 

2566 optional): 

2567 # lobster-trace: LRM.Described_Name_Description 

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

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

2570 assert isinstance(member_of, Composite_Type) 

2571 assert isinstance(optional, bool) 

2572 self.description = description 

2573 self.member_of = member_of 

2574 self.optional = optional 

2575 

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

2577 # lobster-exclude: Debugging feature 

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

2579 if self.description: 

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

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

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

2583 

2584 def __repr__(self): 

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

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

2587 self.name) 

2588 

2589 

2590class Record_Type(Composite_Type): 

2591 """A user-defined record type. 

2592 

2593 In this example:: 

2594 

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

2596 ^1 ^2 ^3 

2597 

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

2599 of the :class:`Composite_Type` base. 

2600 

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

2602 :type: Record_Type 

2603 

2604 :attribute frozen: mapping of frozen components 

2605 :type: dict[str, Expression] 

2606 

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

2608 :type: bool 

2609 

2610 :attribute is_abstract: type is abstract 

2611 :type: bool 

2612 

2613 """ 

2614 def __init__(self, 

2615 name, 

2616 description, 

2617 location, 

2618 package, 

2619 n_parent, 

2620 is_abstract): 

2621 # lobster-exclude: Constructor only declares variables 

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

2623 assert isinstance(is_abstract, bool) 

2624 super().__init__(name, 

2625 description, 

2626 location, 

2627 package, 

2628 n_parent.components if n_parent else None) 

2629 self.parent = n_parent 

2630 self.frozen = {} 

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

2632 self.is_abstract = is_abstract 

2633 

2634 def iter_checks(self): 

2635 # lobster-trace: LRM.Check_Evaluation_Order 

2636 # lobster-trace: LRM.Check_Evaluation_Order_For_Extensions 

2637 if self.parent: 

2638 yield from self.parent.iter_checks() 

2639 yield from self.checks 

2640 

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

2642 # lobster-exclude: Debugging feature 

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

2644 if self.description: 

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

2646 if self.parent: 

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

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

2649 if self.checks: 

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

2651 for n_check in self.checks: 

2652 n_check.dump(indent + 2) 

2653 else: 

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

2655 

2656 def all_components(self): 

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

2658 

2659 :rtype: list[Composite_Component] 

2660 """ 

2661 if self.parent: 

2662 return self.parent.all_components() + \ 

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

2664 else: 

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

2666 

2667 def is_subclass_of(self, record_type): 

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

2669 

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

2671 :type record_type: Record_Type 

2672 

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

2674 :rtype: Boolean 

2675 """ 

2676 assert isinstance(record_type, Record_Type) 

2677 

2678 ptr = self 

2679 while ptr: 

2680 if ptr is record_type: 

2681 return True 

2682 else: 

2683 ptr = ptr.parent 

2684 return False 

2685 

2686 def is_frozen(self, n_component): 

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

2688 

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

2690 (or any of its parents) 

2691 :type n_component: Composite_Component 

2692 

2693 :rtype: bool 

2694 """ 

2695 assert isinstance(n_component, Composite_Component) 

2696 if n_component.name in self.frozen: 

2697 return True 

2698 elif self.parent: 

2699 return self.parent.is_frozen(n_component) 

2700 else: 

2701 return False 

2702 

2703 def get_freezing_expression(self, n_component): 

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

2705 

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

2707 component that his not frozen. 

2708 

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

2710 (or any of its parents) 

2711 :type n_component: Composite_Component 

2712 

2713 :rtype: Expression 

2714 

2715 """ 

2716 assert isinstance(n_component, Composite_Component) 

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

2718 return self.frozen[n_component.name] 

2719 elif self.parent: 

2720 return self.parent.get_freezing_expression(n_component) 

2721 else: 

2722 assert False 

2723 

2724 def get_example_value(self): 

2725 # lobster-exclude: utility method 

2726 return "%s_instance" % self.name 

2727 

2728 

2729class Tuple_Type(Composite_Type): 

2730 """A user-defined tuple type. 

2731 

2732 In this example:: 

2733 

2734 tuple T "optional description of T" { 

2735 ^1 ^2 

2736 

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

2738 of the :class:`Composite_Type` base. 

2739 

2740 :attribute separators: list of syntactic separators. 

2741 :type: list[Separator] 

2742 

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

2744 precisely one less separator than components. 

2745 

2746 """ 

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

2748 # lobster-trace: LRM.Tuple_Declaration 

2749 super().__init__(name, 

2750 description, 

2751 location, 

2752 package) 

2753 self.separators = [] 

2754 

2755 def add_separator(self, n_separator): 

2756 # lobster-exclude: utility method 

2757 assert isinstance(n_separator, Separator) 

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

2759 self.separators.append(n_separator) 

2760 

2761 def iter_separators(self): 

2762 """Iterate over all separators""" 

2763 # lobster-exclude: utility method 

2764 yield from self.separators 

2765 

2766 def iter_sequence(self): 

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

2768 # lobster-exclude: utility method 

2769 if self.separators: 

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

2771 yield n_component 

2772 if i < len(self.separators): 

2773 yield self.separators[i] 

2774 else: 

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

2776 

2777 def has_separators(self): 

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

2779 # lobster-exclude: utility method 

2780 return bool(self.separators) 

2781 

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

2783 # lobster-exclude: Debugging feature 

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

2785 if self.description: 

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

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

2788 for n_item in self.iter_sequence(): 

2789 n_item.dump(indent + 2) 

2790 if self.checks: 

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

2792 for n_check in self.checks: 

2793 n_check.dump(indent + 2) 

2794 else: 

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

2796 

2797 def perform_type_checks(self, mh, value): 

2798 # lobster-trace: LRM.Check_Evaluation_Order 

2799 assert isinstance(mh, Message_Handler) 

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

2801 ok = True 

2802 for check in self.iter_checks(): 

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

2804 ok = False 

2805 return ok 

2806 else: 

2807 assert isinstance(value, Implicit_Null) 

2808 return True 

2809 

2810 def get_example_value(self): 

2811 # lobster-exclude: utility method 

2812 parts = [] 

2813 for n_item in self.iter_sequence(): 

2814 if isinstance(n_item, Composite_Component): 

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

2816 else: 

2817 parts.append(n_item.to_string()) 

2818 if self.has_separators(): 

2819 return " ".join(parts) 

2820 else: 

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

2822 

2823 

2824class Separator(Node): 

2825 # lobster-trace: LRM.Tuple_Declaration 

2826 """User-defined syntactic separator 

2827 

2828 For example:: 

2829 

2830 separator x 

2831 ^1 

2832 

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

2834 :type: Token 

2835 """ 

2836 def __init__(self, token): 

2837 super().__init__(token.location) 

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

2839 "AT", 

2840 "COLON", 

2841 "SEMICOLON") 

2842 self.token = token 

2843 

2844 def to_string(self): 

2845 return { 

2846 "AT" : "@", 

2847 "COLON" : ":", 

2848 "SEMICOLON" : ";" 

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

2850 

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

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

2853 

2854 

2855class Enumeration_Type(Concrete_Type): 

2856 """User-defined enumeration types. 

2857 

2858 For example:: 

2859 

2860 enum T "potato" { 

2861 ^1 ^2 

2862 

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

2864 :type: str 

2865 

2866 :attribute literals: the literals in this enumeration 

2867 :type: Symbol_Table[Enumeration_Literal_Spec] 

2868 

2869 """ 

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

2871 # lobster-trace: LRM.Described_Name_Description 

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

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

2874 self.literals = Symbol_Table() 

2875 self.description = description 

2876 

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

2878 # lobster-exclude: Debugging feature 

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

2880 if self.description: 

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

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

2883 

2884 def get_example_value(self): 

2885 # lobster-exclude: utility method 

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

2887 if options: 

2888 choice = len(options) // 2 

2889 return self.name + "." + choice.name 

2890 else: 

2891 return "ERROR" 

2892 

2893 

2894class Enumeration_Literal_Spec(Typed_Entity): 

2895 """Declared literal in an enumeration declaration. 

2896 

2897 Note that for literals mentioned later in record object 

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

2899 are used here:: 

2900 

2901 enum ASIL { 

2902 QM "not safety related" 

2903 ^1 ^2 

2904 

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

2906 :type: str 

2907 

2908 """ 

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

2910 # lobster-trace: LRM.Described_Name_Description 

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

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

2913 assert isinstance(enum, Enumeration_Type) 

2914 self.description = description 

2915 

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

2917 # lobster-exclude: Debugging feature 

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

2919 if self.description: 

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

2921 

2922 

2923class Record_Object(Typed_Entity): 

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

2925 

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

2927 

2928 section "Potato" { 

2929 ^5 

2930 Requirement PotatoReq { 

2931 ^1 ^2 

2932 component1 = 42 

2933 ^3 ^4 

2934 

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

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

2937 class. 

2938 

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

2940 :type: dict[str, Expression] 

2941 

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

2943 :type: Section 

2944 

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

2946 :type: Section 

2947 

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

2949 to: 

2950 

2951 * :class:`Literal` 

2952 * :class:`Unary_Expression` 

2953 * :class:`Array_Aggregate` 

2954 * :class:`Tuple_Aggregate` 

2955 * :class:`Record_Reference` 

2956 * :class:`Implicit_Null` 

2957 

2958 """ 

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

2960 # lobster-trace: LRM.Section_Declaration 

2961 # lobster-trace: LRM.Unspecified_Optional_Components 

2962 # lobster-trace: LRM.Record_Object_Declaration 

2963 

2964 assert isinstance(n_typ, Record_Type) 

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

2966 assert isinstance(n_package, Package) 

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

2968 self.field = { 

2969 comp.name: Implicit_Null(self, comp) 

2970 for comp in self.n_typ.all_components() 

2971 } 

2972 self.section = section 

2973 self.n_package = n_package 

2974 

2975 def fully_qualified_name(self): 

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

2977 

2978 :returns: the object's full name 

2979 :rtype: str 

2980 """ 

2981 return self.n_package.name + "." + self.name 

2982 

2983 def to_python_dict(self): 

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

2985 

2986 For example it might provide:: 

2987 

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

2989 "bar" : None, 

2990 "baz" : "value"} 

2991 

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

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

2994 

2995 """ 

2996 return {name: value.to_python_object() 

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

2998 

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

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

3001 

3002 def assign(self, component, value): 

3003 assert isinstance(component, Composite_Component) 

3004 assert isinstance(value, (Literal, 

3005 Array_Aggregate, 

3006 Tuple_Aggregate, 

3007 Record_Reference, 

3008 Implicit_Null, 

3009 Unary_Expression)), \ 

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

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

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

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

3014 self.field[component.name] = value 

3015 

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

3017 # lobster-exclude: Debugging feature 

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

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

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

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

3022 value.dump(indent + 2) 

3023 if self.section: 

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

3025 

3026 def resolve_references(self, mh): 

3027 assert isinstance(mh, Message_Handler) 

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

3029 val.resolve_references(mh) 

3030 

3031 def perform_checks(self, mh): 

3032 # lobster-trace: LRM.Check_Evaluation_Order 

3033 # lobster-trace: LRM.Evaluation_Of_Checks 

3034 assert isinstance(mh, Message_Handler) 

3035 

3036 ok = True 

3037 

3038 # First evaluate all tuple checks 

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

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

3041 self.field[n_comp.name]): 

3042 ok = False 

3043 

3044 # Then evaluate all record checks 

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

3046 # Prints messages, if applicable. Raises exception on 

3047 # fatal checks, which causes this to abort. 

3048 if not check.perform(mh, self): 

3049 ok = False 

3050 

3051 return ok 

3052 

3053 def __repr__(self): 

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

3055 self.n_package.name + "." + 

3056 self.n_typ.name + "." + 

3057 self.name) 

3058 

3059 

3060class Section(Entity): 

3061 # lobster-trace: LRM.Section_Declaration 

3062 """A section for readability 

3063 

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

3065 objects together:: 

3066 

3067 section "Foo" { 

3068 ^^^^^ parent section 

3069 section "Bar" { 

3070 ^^^^^ section 

3071 

3072 :attribute parent: the parent section or None 

3073 :type: Section 

3074 

3075 """ 

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

3077 super().__init__(name, location) 

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

3079 self.parent = parent 

3080 

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

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

3083 if self.parent is None: 

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

3085 else: 

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

3087 

3088 

3089############################################################################## 

3090# Symbol Table & Scopes 

3091############################################################################## 

3092 

3093class Symbol_Table: 

3094 """ Symbol table mapping names to entities 

3095 """ 

3096 def __init__(self, parent=None): 

3097 # lobster-exclude: Constructor only declares variables 

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

3099 self.parent = parent 

3100 self.imported = [] 

3101 self.table = OrderedDict() 

3102 self.trlc_files = [] 

3103 self.section_names = [] 

3104 

3105 @staticmethod 

3106 def simplified_name(name): 

3107 # lobster-trace: LRM.Sufficiently_Distinct 

3108 assert isinstance(name, str) 

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

3110 

3111 def all_names(self): 

3112 # lobster-exclude: API for users 

3113 """ All names in the symbol table 

3114 

3115 :rtype: set[str] 

3116 """ 

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

3118 if self.parent: 

3119 rv |= self.parent.all_names() 

3120 return rv 

3121 

3122 def iter_record_objects_by_section(self): 

3123 """API for users 

3124 

3125 Retriving information about the section hierarchy for record objects 

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

3127 sub sections and record objects 

3128 Output: Information about sections and level of sections, 

3129 record objects and levels of record object 

3130 """ 

3131 for record_object in self.iter_record_objects(): 

3132 location = record_object.location.file_name 

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

3134 self.trlc_files.append(location) 

3135 yield location 

3136 if record_object.section: 

3137 object_level = len(record_object.section) - 1 

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

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

3140 self.section_names.append(section) 

3141 yield section.name, level 

3142 yield record_object, object_level 

3143 else: 

3144 object_level = 0 

3145 yield record_object, object_level 

3146 

3147 def iter_record_objects(self): 

3148 # lobster-exclude: API for users 

3149 """ Iterate over all record objects 

3150 

3151 :rtype: iterable[Record_Object] 

3152 """ 

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

3154 if isinstance(item, Package): 

3155 yield from item.symbols.iter_record_objects() 

3156 

3157 elif isinstance(item, Record_Object): 

3158 yield item 

3159 

3160 def values(self, subtype=None): 

3161 # lobster-exclude: API for users 

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

3163 if self.parent: 

3164 yield from self.parent.values(subtype) 

3165 for name in sorted(self.table): 

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

3167 yield self.table[name] 

3168 

3169 def make_visible(self, stab): 

3170 assert isinstance(stab, Symbol_Table) 

3171 self.imported.append(stab) 

3172 

3173 def register(self, mh, entity): 

3174 # lobster-trace: LRM.Duplicate_Types 

3175 # lobster-trace: LRM.Unique_Enumeration_Literals 

3176 # lobster-trace: LRM.Tuple_Unique_Field_Names 

3177 # lobster-trace: LRM.Sufficiently_Distinct 

3178 # lobster-trace: LRM.Unique_Object_Names 

3179 

3180 assert isinstance(mh, Message_Handler) 

3181 assert isinstance(entity, Entity) 

3182 

3183 simple_name = self.simplified_name(entity.name) 

3184 

3185 if self.contains_raw(simple_name): 

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

3187 simplified=True) 

3188 if pdef.name == entity.name: 

3189 mh.error(entity.location, 

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

3191 mh.cross_file_reference(pdef.location)) 

3192 else: 

3193 mh.error(entity.location, 

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

3195 (entity.name, 

3196 pdef.name, 

3197 mh.cross_file_reference(pdef.location))) 

3198 

3199 else: 

3200 self.table[simple_name] = entity 

3201 

3202 def __contains__(self, name): 

3203 # lobster-trace: LRM.Described_Name_Equality 

3204 return self.contains(name) 

3205 

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

3207 # lobster-trace: LRM.Described_Name_Equality 

3208 # lobster-trace: LRM.Sufficiently_Distinct 

3209 # 

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

3211 # table. 

3212 assert isinstance(simple_name, str) 

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

3214 

3215 if simple_name in self.table: 

3216 # No need to continue searching since registering a 

3217 # clashing name would have been stopped 

3218 return precise_name is None or \ 

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

3220 

3221 elif self.parent: 

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

3223 

3224 for stab in self.imported: 

3225 if stab.contains_raw(simple_name, precise_name): 

3226 return True 

3227 

3228 return False 

3229 

3230 def contains(self, name): 

3231 # lobster-trace: LRM.Described_Name_Equality 

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

3233 

3234 :param name: the name to test 

3235 :type name: str 

3236 

3237 :rtype: bool 

3238 """ 

3239 assert isinstance(name, str) 

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

3241 

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

3243 # lobster-trace: LRM.Described_Name_Equality 

3244 # lobster-trace: LRM.Sufficiently_Distinct 

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

3246 

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

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

3249 

3250 :param mh: The message handler to use 

3251 :type mh: Message_Handler 

3252 

3253 :param name: The name to search for 

3254 :type name: str 

3255 

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

3257 is not an instance of the given class 

3258 :type required_subclass: type 

3259 

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

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

3262 :rtype: Entity 

3263 

3264 """ 

3265 assert isinstance(mh, Message_Handler) 

3266 assert isinstance(name, str) 

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

3268 

3269 simple_name = self.simplified_name(name) 

3270 

3271 ptr = self 

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

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

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

3275 rv = ptr.table[simple_name] 

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

3277 return None 

3278 

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

3280 not isinstance(rv, required_subclass): 

3281 mh.error(rv.location, 

3282 "%s %s is not a %s" % 

3283 (rv.__class__.__name__, 

3284 name, 

3285 required_subclass.__name__)) 

3286 return rv 

3287 else: 

3288 ptr = ptr.parent 

3289 

3290 return None 

3291 

3292 def lookup_direct(self, 

3293 mh, 

3294 name, 

3295 error_location, 

3296 required_subclass=None, 

3297 simplified=False): 

3298 # lobster-trace: LRM.Described_Name_Equality 

3299 # lobster-trace: LRM.Sufficiently_Distinct 

3300 # lobster-trace: LRM.Valid_Base_Names 

3301 # lobster-trace: LRM.Valid_Access_Prefixes 

3302 # lobster-trace: LRM.Valid_Function_Prefixes 

3303 """Retrieve an object from the table 

3304 

3305 For example:: 

3306 

3307 pkg = stab.lookup_direct(mh, 

3308 "potato", 

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

3310 Package) 

3311 

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

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

3314 Package, then the following error is issued:: 

3315 

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

3317 

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

3319 

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

3321 

3322 :param mh: The message handler to use 

3323 :type mh: Message_Handler 

3324 

3325 :param name: The name to search for 

3326 :type name: str 

3327 

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

3329 not found 

3330 :type error_location: Location 

3331 

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

3333 is not an instance of the given class 

3334 :type required_subclass: type 

3335 

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

3337 of the actual name 

3338 :type simplified: bool 

3339 

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

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

3342 :returns: the specified entity 

3343 :rtype: Entity 

3344 

3345 """ 

3346 assert isinstance(mh, Message_Handler) 

3347 assert isinstance(name, str) 

3348 assert isinstance(error_location, Location) 

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

3350 assert isinstance(simplified, bool) 

3351 

3352 simple_name = self.simplified_name(name) 

3353 ptr = self 

3354 options = [] 

3355 

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

3357 while ptr: 

3358 if simple_name in ptr.table: 

3359 rv = ptr.table[simple_name] 

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

3361 mh.error(error_location, 

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

3363 (name, 

3364 rv.name)) 

3365 

3366 if required_subclass is not None and \ 

3367 not isinstance(rv, required_subclass): 

3368 mh.error(error_location, 

3369 "%s %s is not a %s" % 

3370 (rv.__class__.__name__, 

3371 name, 

3372 required_subclass.__name__)) 

3373 return rv 

3374 else: 

3375 options += list(item.name 

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

3377 ptr = ptr.parent 

3378 

3379 matches = get_close_matches( 

3380 word = name, 

3381 possibilities = options, 

3382 n = 1) 

3383 

3384 if matches: 

3385 mh.error(error_location, 

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

3387 (name, 

3388 matches[0])) 

3389 else: 

3390 mh.error(error_location, 

3391 "unknown symbol %s" % name) 

3392 

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

3394 # lobster-trace: LRM.Described_Name_Equality 

3395 assert isinstance(mh, Message_Handler) 

3396 assert isinstance(referencing_token, Token) 

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

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

3399 

3400 return self.lookup_direct( 

3401 mh = mh, 

3402 name = referencing_token.value, 

3403 error_location = referencing_token.location, 

3404 required_subclass = required_subclass) 

3405 

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

3407 # lobster-exclude: Debugging feature 

3408 assert isinstance(indent, int) 

3409 assert indent >= 0 

3410 assert isinstance(message, str) 

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

3412 

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

3414 # lobster-exclude: Debugging feature 

3415 if omit_heading: 

3416 new_indent = indent 

3417 else: 

3418 self.write_indent(indent, "Symbol_Table") 

3419 new_indent = indent + 1 

3420 ptr = self 

3421 while ptr: 

3422 for name in ptr.table: 

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

3424 ptr = ptr.parent 

3425 

3426 @classmethod 

3427 def create_global_table(cls, mh): 

3428 # lobster-trace: LRM.Builtin_Types 

3429 # lobster-trace: LRM.Builtin_Functions 

3430 # lobster-trace: LRM.Builtin_Type_Conversion_Functions 

3431 # lobster-trace: LRM.Signature_Len 

3432 # lobster-trace: LRM.Signature_String_End_Functions 

3433 # lobster-trace: LRM.Signature_Matches 

3434 

3435 stab = Symbol_Table() 

3436 stab.register(mh, Builtin_Integer()) 

3437 stab.register(mh, Builtin_Decimal()) 

3438 stab.register(mh, Builtin_Boolean()) 

3439 stab.register(mh, Builtin_String()) 

3440 stab.register(mh, Builtin_Markup_String()) 

3441 stab.register(mh, 

3442 Builtin_Function("len", 1)) 

3443 stab.register(mh, 

3444 Builtin_Function("startswith", 2)) 

3445 stab.register(mh, 

3446 Builtin_Function("endswith", 2)) 

3447 stab.register(mh, 

3448 Builtin_Function("matches", 2)) 

3449 stab.register(mh, 

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

3451 

3452 return stab 

3453 

3454 

3455class Scope: 

3456 def __init__(self): 

3457 # lobster-exclude: Constructor only declares variables 

3458 self.scope = [] 

3459 

3460 def push(self, stab): 

3461 assert isinstance(stab, Symbol_Table) 

3462 self.scope.append(stab) 

3463 

3464 def pop(self): 

3465 self.scope.pop() 

3466 

3467 def contains(self, name): 

3468 assert isinstance(name, str) 

3469 

3470 for stab in reversed(self.scope): 

3471 if stab.contains(name): 

3472 return True 

3473 return False 

3474 

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

3476 assert len(self.scope) >= 1 

3477 assert isinstance(mh, Message_Handler) 

3478 assert isinstance(referencing_token, Token) 

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

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

3481 

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

3483 if stab.contains(referencing_token.value): 

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

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

3486 

3487 def size(self): 

3488 return len(self.scope)