Coverage for trlc/ast.py: 90%

1231 statements  

« prev     ^ index     » next       coverage.py v7.10.7, created at 2026-01-15 09:56 +0000

1#!/usr/bin/env python3 

2# 

3# TRLC - Treat Requirements Like Code 

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

5# Copyright (C) 2024-2025 Florian Schanda 

6# 

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

8# 

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

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

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

12# (at your option) any later version. 

13# 

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

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

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

17# License for more details. 

18# 

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

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

21 

22from abc import ABCMeta, abstractmethod 

23import re 

24 

25from copy import copy 

26from difflib import get_close_matches 

27from enum import Enum, auto 

28from collections import OrderedDict 

29from fractions import Fraction 

30 

31from trlc.errors import TRLC_Error, Location, Message_Handler 

32from trlc.lexer import Token 

33from trlc import math 

34 

35# 

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

37# reference implementation. There are four sections: 

38# 

39# - Valuations deal with concrete values for record objects 

40# - AST expressions deal with the syntax tree 

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

42# - Symbol_Table and scope deals with name resolution 

43# 

44 

45 

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

47# Valuations 

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

49 

50class Value: 

51 # lobster-trace: LRM.Boolean_Values 

52 # lobster-trace: LRM.Integer_Values 

53 # lobster-trace: LRM.Decimal_Values 

54 # lobster-trace: LRM.String_Values 

55 # lobster-trace: LRM.Markup_String_Values 

56 """Polymorphic value for evaluating expressions. 

57 

58 Any record references will be fully resolved. 

59 

60 :attribute location: source location this value comes from 

61 :type: Location 

62 

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

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

65 Record_Reference, Enumeration_Literal_Spec 

66 

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

68 :type: Type 

69 """ 

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

71 assert isinstance(location, Location) 

72 assert value is None or \ 

73 isinstance(value, (str, 

74 int, 

75 bool, 

76 list, # for arrays 

77 dict, # for tuples 

78 Fraction, 

79 Record_Reference, 

80 Enumeration_Literal_Spec)) 

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

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

83 

84 self.location = location 

85 self.value = value 

86 self.typ = typ 

87 

88 def __eq__(self, other): 

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

90 

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

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

93 

94 def resolve_references(self, mh): 

95 assert isinstance(mh, Message_Handler) 

96 

97 if isinstance(self.value, Record_Reference): 

98 self.value.resolve(mh) 

99 

100 

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

102# AST Nodes 

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

104 

105class Node(metaclass=ABCMeta): 

106 """Base class for all AST items. 

107 

108 :attribute location: source location 

109 :type: Location 

110 """ 

111 def __init__(self, location): 

112 # lobster-exclude: Constructor only declares variables 

113 assert isinstance(location, Location) 

114 self.location = location 

115 

116 def set_ast_link(self, tok): 

117 assert isinstance(tok, Token) 

118 tok.ast_link = self 

119 

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

121 # lobster-exclude: Debugging feature 

122 assert isinstance(indent, int) 

123 assert indent >= 0 

124 assert isinstance(message, str) 

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

126 

127 @abstractmethod 

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

129 """Visualise the parse tree. 

130 

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

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

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

134 output like this:: 

135 

136 Symbol_Table 

137 Builtin_Boolean 

138 Builtin_Integer 

139 Builtin_Decimal 

140 Builtin_String 

141 Builtin_Markup_String 

142 Package bar 

143 Symbol_Table 

144 Record_Type MyType 

145 Composite_Component name 

146 Optional: False 

147 Type: String 

148 Checks 

149 Error 'description is too short' 

150 Anchor: description 

151 Binary Binary_Operator.COMP_GT Expression 

152 Type: Boolean 

153 Unary Unary_Operator.STRING_LENGTH Expression 

154 Type: Integer 

155 Name Reference to description 

156 Integer Literal 10 

157 Package instances 

158 Symbol_Table 

159 Record_Object SomeThing 

160 Type: MyType 

161 Field description: "Potato" 

162 Builtin_Function endswith 

163 Builtin_Function len 

164 Builtin_Function matches 

165 Builtin_Function startswith 

166 Builtin_Function oneof 

167 

168 """ 

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

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

171 # lobster-exclude: Debugging feature 

172 

173 

174class Check_Block(Node): 

175 """Node representing check blocks 

176 

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

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

179 

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

181 :type: Composite_Type 

182 

183 :attribute checks: list of checks 

184 :type: list[Check] 

185 

186 """ 

187 def __init__(self, location, n_typ): 

188 # lobster-trace: LRM.Check_Block 

189 super().__init__(location) 

190 assert isinstance(n_typ, Composite_Type) 

191 self.n_typ = n_typ 

192 self.checks = [] 

193 

194 def add_check(self, n_check): 

195 # lobster-trace: LRM.Check_Evaluation_Order 

196 assert isinstance(n_check, Check) 

197 self.checks.append(n_check) 

198 

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

200 # lobster-exclude: Debugging feature 

201 self.write_indent(indent, "Check_Block") 

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

203 for n_check in self.checks: 

204 n_check.dump(indent + 1) 

205 

206 

207class Compilation_Unit(Node): 

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

209 

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

211 :type: Package 

212 

213 :attribute imports: package imported by this file 

214 :type: list[Package] 

215 

216 :attribute items: list of 

217 :type: list[Node] 

218 

219 """ 

220 def __init__(self, file_name): 

221 # lobster-exclude: Constructor only declares variables 

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

223 self.package = None 

224 self.imports = None 

225 self.raw_imports = [] 

226 self.items = [] 

227 

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

229 # lobster-exclude: Debugging feature 

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

231 for t_import in self.raw_imports: 

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

233 for n_item in self.items: 

234 n_item.dump(indent + 1) 

235 

236 def set_package(self, pkg): 

237 # lobster-trace: LRM.Current_Package 

238 assert isinstance(pkg, Package) 

239 self.package = pkg 

240 

241 def add_import(self, mh, t_import): 

242 # lobster-trace: LRM.Import_Visibility 

243 # lobster-trace: LRM.Self_Imports 

244 assert isinstance(mh, Message_Handler) 

245 assert isinstance(t_import, Token) 

246 assert t_import.kind == "IDENTIFIER" 

247 

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

249 mh.error(t_import.location, 

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

251 

252 # Skip duplicates 

253 for t_previous in self.raw_imports: 

254 if t_previous.value == t_import.value: 

255 mh.warning(t_import.location, 

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

257 return 

258 

259 self.raw_imports.append(t_import) 

260 

261 def resolve_imports(self, mh, stab): 

262 # lobster-trace: LRM.Import_Visibility 

263 assert isinstance(mh, Message_Handler) 

264 assert isinstance(stab, Symbol_Table) 

265 self.imports = set() 

266 for t_import in self.raw_imports: 

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

268 # generate more error later. 

269 try: 

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

271 self.imports.add(a_import) 

272 a_import.set_ast_link(t_import) 

273 except TRLC_Error: 

274 pass 

275 

276 def is_visible(self, n_pkg): 

277 # lobster-trace: LRM.Import_Visibility 

278 assert self.imports is not None 

279 assert isinstance(n_pkg, Package) 

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

281 

282 def add_item(self, node): 

283 # lobster-trace: LRM.RSL_File 

284 # lobster-trace: LRM.TRLC_File 

285 assert isinstance(node, (Concrete_Type, 

286 Check_Block, 

287 Record_Object)), \ 

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

289 self.items.append(node) 

290 

291 

292class Check(Node): 

293 """User defined check 

294 

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

296 

297 checks T { 

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

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

300 

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

302 :type: Composite_Type 

303 

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

305 :type: Expression 

306 

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

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

309 :type: Composite_Component 

310 

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

312 not specified the default is 'error') 

313 :type: str 

314 

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

316 :type: str 

317 """ 

318 def __init__(self, 

319 n_type, 

320 n_expr, 

321 n_anchor, 

322 severity, 

323 t_message, 

324 extrainfo): 

325 # lobster-trace: LRM.Check_Block 

326 assert isinstance(n_type, Composite_Type) 

327 assert isinstance(n_expr, Expression) 

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

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

330 assert isinstance(t_message, Token) 

331 assert t_message.kind == "STRING" 

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

333 super().__init__(t_message.location) 

334 

335 self.n_type = n_type 

336 self.n_expr = n_expr 

337 self.n_anchor = n_anchor 

338 self.severity = severity 

339 # lobster-trace: LRM.No_Newlines_In_Message 

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

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

342 # raised is non-fatal. 

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

344 self.extrainfo = extrainfo 

345 

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

347 # lobster-exclude: Debugging feature 

348 if self.severity == "warning": 

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

350 elif self.severity == "error": 

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

352 else: 

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

354 if self.n_anchor: 

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

356 self.n_expr.dump(indent + 1) 

357 

358 def get_real_location(self, composite_object): 

359 # lobster-exclude: LRM.Anchoring 

360 assert isinstance(composite_object, (Record_Object, 

361 Tuple_Aggregate)) 

362 if isinstance(composite_object, Record_Object): 

363 fields = composite_object.field 

364 else: 

365 fields = composite_object.value 

366 

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

368 return composite_object.location 

369 else: 

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

371 

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

373 # lobster-trace: LRM.Check_Messages 

374 # lobster-trace: LRM.Check_Severity 

375 assert isinstance(mh, Message_Handler) 

376 assert isinstance(composite_object, (Record_Object, 

377 Tuple_Aggregate)) 

378 assert isinstance(gstab, Symbol_Table) 

379 

380 if isinstance(composite_object, Record_Object): 

381 result = self.n_expr.evaluate(mh, 

382 copy(composite_object.field), 

383 gstab) 

384 else: 

385 result = self.n_expr.evaluate(mh, 

386 copy(composite_object.value), 

387 gstab) 

388 if result.value is None: 

389 loc = self.get_real_location(composite_object) 

390 mh.error(loc, 

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

392 (self.n_expr.to_string(), 

393 mh.cross_file_reference(self.location))) 

394 

395 assert isinstance(result.value, bool) 

396 

397 if not result.value: 

398 loc = self.get_real_location(composite_object) 

399 if self.severity == "warning": 

400 mh.warning(location = loc, 

401 message = self.message, 

402 explanation = self.extrainfo, 

403 user = True) 

404 else: 

405 mh.error(location = loc, 

406 message = self.message, 

407 explanation = self.extrainfo, 

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

409 user = True) 

410 return False 

411 

412 return True 

413 

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

415# AST Nodes (Expressions) 

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

417 

418 

419class Unary_Operator(Enum): 

420 # lobster-exclude: Utility enumeration for unary operators 

421 MINUS = auto() 

422 PLUS = auto() 

423 LOGICAL_NOT = auto() 

424 ABSOLUTE_VALUE = auto() 

425 

426 STRING_LENGTH = auto() 

427 ARRAY_LENGTH = auto() 

428 

429 CONVERSION_TO_INT = auto() 

430 CONVERSION_TO_DECIMAL = auto() 

431 

432 

433class Binary_Operator(Enum): 

434 # lobster-exclude: Utility enumeration for binary operators 

435 LOGICAL_AND = auto() # Short-circuit 

436 LOGICAL_OR = auto() # Short-circuit 

437 LOGICAL_XOR = auto() 

438 LOGICAL_IMPLIES = auto() # Short-circuit 

439 

440 COMP_EQ = auto() 

441 COMP_NEQ = auto() 

442 COMP_LT = auto() 

443 COMP_LEQ = auto() 

444 COMP_GT = auto() 

445 COMP_GEQ = auto() 

446 

447 STRING_CONTAINS = auto() 

448 STRING_STARTSWITH = auto() 

449 STRING_ENDSWITH = auto() 

450 STRING_REGEX = auto() 

451 

452 ARRAY_CONTAINS = auto() 

453 

454 PLUS = auto() 

455 MINUS = auto() 

456 TIMES = auto() 

457 DIVIDE = auto() 

458 REMAINDER = auto() 

459 

460 POWER = auto() 

461 

462 INDEX = auto() 

463 

464 

465class Expression(Node, metaclass=ABCMeta): 

466 """Abstract base class for all expressions. 

467 

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

469 :type: Type 

470 """ 

471 def __init__(self, location, typ): 

472 # lobster-exclude: Constructor only declares variables 

473 super().__init__(location) 

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

475 self.typ = typ 

476 

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

478 """Evaluate the expression in the given context 

479 

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

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

482 dictionary that maps names (such as record fields or 

483 quantified variables) to expressions. 

484 

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

486 evaluations), otherwise it must contain the global symbol 

487 table to resolve record references. 

488 

489 :param mh: the message handler to use 

490 :type mh: Message_Handler 

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

492 :type context: dict[str, Expression] 

493 :raise TRLC_Error: if the expression cannot be evaluated 

494 :return: result of the evaluation 

495 :rtype: Value 

496 

497 """ 

498 assert isinstance(mh, Message_Handler) 

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

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

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

502 self.__class__.__name__ 

503 

504 @abstractmethod 

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

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

507 self.__class__.__name__ 

508 

509 def ensure_type(self, mh, typ): 

510 # lobster-trace: LRM.Restricted_Null 

511 # lobster-trace: LRM.Null_Is_Invalid 

512 

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

514 if self.typ is None: 

515 mh.error(self.location, 

516 "null is not permitted here") 

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

518 mh.error(self.location, 

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

520 (typ.__name__, 

521 self.typ.__class__.__name__)) 

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

523 mh.error(self.location, 

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

525 (typ.name, 

526 self.typ.name)) 

527 

528 def resolve_references(self, mh): 

529 assert isinstance(mh, Message_Handler) 

530 

531 @abstractmethod 

532 def can_be_null(self): 

533 """Test if the expression could return null 

534 

535 Checks the expression if it could generate a null value 

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

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

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

539 occur earlier. 

540 

541 :return: possibility of encountering null 

542 :rtype: bool 

543 

544 """ 

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

546 self.__class__.__name__ 

547 

548 

549class Implicit_Null(Expression): 

550 """Synthesised null values 

551 

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

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

554 implicit null expression for this. 

555 

556 For example given this TRLC type:: 

557 

558 type T { 

559 x optional Integer 

560 } 

561 

562 And this declaration:: 

563 

564 T Potato {} 

565 

566 Then the field mapping for Potato will be:: 

567 

568 {x: Implicit_Null} 

569 

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

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

572 that can appear in check expressions. 

573 

574 """ 

575 def __init__(self, composite_object, composite_component): 

576 # lobster-trace: LRM.Unspecified_Optional_Components 

577 assert isinstance(composite_object, (Record_Object, 

578 Tuple_Aggregate)) 

579 assert isinstance(composite_component, Composite_Component) 

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

581 

582 def to_string(self): 

583 return "null" 

584 

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

586 # lobster-trace: LRM.Unspecified_Optional_Components 

587 assert isinstance(mh, Message_Handler) 

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

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

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

591 

592 def to_python_object(self): 

593 return None 

594 

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

596 # lobster-exclude: Debugging feature 

597 self.write_indent(indent, "Implicit_Null") 

598 

599 def can_be_null(self): 

600 return True 

601 

602 

603class Literal(Expression, metaclass=ABCMeta): 

604 """Abstract base for all Literals 

605 

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

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

608 check if you are dealing with a literal:: 

609 

610 isinstance(my_expression, Literal) 

611 

612 """ 

613 @abstractmethod 

614 def to_python_object(self): 

615 assert False 

616 

617 

618class Null_Literal(Literal): 

619 # lobster-trace: LRM.Primary 

620 """The null literal 

621 

622 This can appear in check expressions:: 

623 

624 a /= null implies a > 5 

625 ^^^^ 

626 

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

628 values that appear in record objects. 

629 

630 """ 

631 def __init__(self, token): 

632 assert isinstance(token, Token) 

633 assert token.kind == "KEYWORD" 

634 assert token.value == "null" 

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

636 

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

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

639 

640 def to_string(self): 

641 return "null" 

642 

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

644 assert isinstance(mh, Message_Handler) 

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

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

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

648 

649 def to_python_object(self): 

650 return None 

651 

652 def can_be_null(self): 

653 return True 

654 

655 

656class Integer_Literal(Literal): 

657 # lobster-trace: LRM.Integer_Values 

658 # lobster-trace: LRM.Primary 

659 """Integer literals 

660 

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

662 actually a unary negation expression, operating on a positive 

663 integer literal:: 

664 

665 x == -5 

666 

667 This would create the following tree:: 

668 

669 OP_EQUALITY 

670 NAME_REFERENCE x 

671 UNARY_EXPRESSION - 

672 INTEGER_LITERAL 5 

673 

674 :attribute value: the non-negative integer value 

675 :type: int 

676 """ 

677 def __init__(self, token, typ): 

678 assert isinstance(token, Token) 

679 assert token.kind == "INTEGER" 

680 assert isinstance(typ, Builtin_Integer) 

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

682 

683 self.value = token.value 

684 

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

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

687 

688 def to_string(self): 

689 return str(self.value) 

690 

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

692 assert isinstance(mh, Message_Handler) 

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

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

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

696 

697 def to_python_object(self): 

698 return self.value 

699 

700 def can_be_null(self): 

701 return False 

702 

703 

704class Decimal_Literal(Literal): 

705 # lobster-trace: LRM.Decimal_Values 

706 # lobster-trace: LRM.Primary 

707 """Decimal literals 

708 

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

710 actually a unary negation expression, operating on a positive 

711 decimal literal:: 

712 

713 x == -5.0 

714 

715 This would create the following tree:: 

716 

717 OP_EQUALITY 

718 NAME_REFERENCE x 

719 UNARY_EXPRESSION - 

720 DECIMAL_LITERAL 5.0 

721 

722 :attribute value: the non-negative decimal value 

723 :type: fractions.Fraction 

724 """ 

725 def __init__(self, token, typ): 

726 assert isinstance(token, Token) 

727 assert token.kind == "DECIMAL" 

728 assert isinstance(typ, Builtin_Decimal) 

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

730 

731 self.value = token.value 

732 

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

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

735 

736 def to_string(self): 

737 return str(self.value) 

738 

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

740 assert isinstance(mh, Message_Handler) 

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

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

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

744 

745 def to_python_object(self): 

746 return float(self.value) 

747 

748 def can_be_null(self): 

749 return False 

750 

751 

752class String_Literal(Literal): 

753 # lobster-trace: LRM.String_Values 

754 # lobster-trace: LRM.Markup_String_Values 

755 # lobster-trace: LRM.Primary 

756 """String literals 

757 

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

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

760 

761 "foo\\"bar" 

762 

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

764 

765 :attribute value: string content 

766 :type: str 

767 

768 :attribute references: resolved references of a markup string 

769 :type: list[Record_Reference] 

770 

771 """ 

772 def __init__(self, token, typ): 

773 assert isinstance(token, Token) 

774 assert token.kind == "STRING" 

775 assert isinstance(typ, Builtin_String) 

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

777 

778 self.value = token.value 

779 self.has_references = isinstance(typ, Builtin_Markup_String) 

780 self.references = [] 

781 

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

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

784 if self.has_references: 

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

786 for ref in self.references: 

787 ref.dump(indent + 2) 

788 

789 def to_string(self): 

790 return self.value 

791 

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

793 assert isinstance(mh, Message_Handler) 

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

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

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

797 

798 def to_python_object(self): 

799 return self.value 

800 

801 def resolve_references(self, mh): 

802 assert isinstance(mh, Message_Handler) 

803 for ref in self.references: 

804 ref.resolve_references(mh) 

805 

806 def can_be_null(self): 

807 return False 

808 

809 

810class Boolean_Literal(Literal): 

811 # lobster-trace: LRM.Boolean_Values 

812 # lobster-trace: LRM.Primary 

813 """Boolean values 

814 

815 :attribute value: the boolean value 

816 :type: bool 

817 """ 

818 def __init__(self, token, typ): 

819 assert isinstance(token, Token) 

820 assert token.kind == "KEYWORD" 

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

822 assert isinstance(typ, Builtin_Boolean) 

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

824 

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

826 

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

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

829 

830 def to_string(self): 

831 return str(self.value) 

832 

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

834 assert isinstance(mh, Message_Handler) 

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

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

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

838 

839 def to_python_object(self): 

840 return self.value 

841 

842 def can_be_null(self): 

843 return False 

844 

845 

846class Enumeration_Literal(Literal): 

847 """Enumeration values 

848 

849 Note that this is distinct from 

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

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

852 

853 foo != my_enum.POTATO 

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

855 

856 To get to the string value of the enumeration literal 

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

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

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

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

861 

862 :attribute value: enumeration value 

863 :type: Enumeration_Literal_Spec 

864 

865 """ 

866 def __init__(self, location, literal): 

867 # lobster-exclude: Constructor only declares variables 

868 assert isinstance(literal, Enumeration_Literal_Spec) 

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

870 

871 self.value = literal 

872 

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

874 # lobster-exclude: Debugging feature 

875 self.write_indent(indent, 

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

877 

878 def to_string(self): 

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

880 

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

882 assert isinstance(mh, Message_Handler) 

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

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

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

886 

887 def to_python_object(self): 

888 return self.value.name 

889 

890 def can_be_null(self): 

891 return False 

892 

893 

894class Array_Aggregate(Expression): 

895 """Instances of array types 

896 

897 This is created when assigning to array components:: 

898 

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

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

901 

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

903 limited: 

904 

905 * :class:`Literal` 

906 * :class:`Array_Aggregate` 

907 * :class:`Record_Reference` 

908 

909 :attribute value: contents of the array 

910 :type: list[Expression] 

911 

912 """ 

913 def __init__(self, location, typ): 

914 # lobster-trace: LRM.Record_Object_Declaration 

915 

916 super().__init__(location, typ) 

917 self.value = [] 

918 

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

920 # lobster-exclude: Debugging feature 

921 self.write_indent(indent, "Array_Aggregate") 

922 for n_value in self.value: 

923 n_value.dump(indent + 1) 

924 

925 def append(self, value): 

926 assert isinstance(value, (Literal, 

927 Unary_Expression, 

928 Array_Aggregate, 

929 Tuple_Aggregate, 

930 Record_Reference)) 

931 self.value.append(value) 

932 

933 def to_string(self): 

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

935 

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

937 assert isinstance(mh, Message_Handler) 

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

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

940 return Value(self.location, 

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

942 for element in self.value), 

943 self.typ) 

944 

945 def resolve_references(self, mh): 

946 assert isinstance(mh, Message_Handler) 

947 

948 for val in self.value: 

949 val.resolve_references(mh) 

950 

951 def to_python_object(self): 

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

953 

954 def can_be_null(self): 

955 return False 

956 

957 

958class Tuple_Aggregate(Expression): 

959 """Instances of a tuple 

960 

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

962 two forms, the ordinary form:: 

963 

964 coordinate = (12.3, 40.0) 

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

966 

967 And the separator form:: 

968 

969 item = 12345@42 

970 ^^^^^^^^ 

971 

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

973 syntactic sugar. 

974 

975 :attribute value: contents of the tuple 

976 :type: dict[str, Expression] 

977 

978 """ 

979 def __init__(self, location, typ): 

980 # lobster-trace: LRM.Unspecified_Optional_Components 

981 # lobster-trace: LRM.Record_Object_Declaration 

982 

983 super().__init__(location, typ) 

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

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

986 

987 def assign(self, field, value): 

988 assert isinstance(field, str) 

989 assert isinstance(value, (Literal, 

990 Unary_Expression, 

991 Tuple_Aggregate, 

992 Record_Reference)), \ 

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

994 assert field in self.typ.components 

995 

996 self.value[field] = value 

997 

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

999 # lobster-exclude: Debugging feature 

1000 self.write_indent(indent, "Tuple_Aggregate") 

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

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

1003 if isinstance(n_item, Composite_Component): 

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

1005 

1006 def to_string(self): 

1007 first = True 

1008 if self.typ.has_separators(): 

1009 rv = "" 

1010 else: 

1011 rv = "(" 

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

1013 if isinstance(n_item, Separator): 

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

1015 elif first: 

1016 first = False 

1017 else: 

1018 rv += ", " 

1019 

1020 if isinstance(n_item, Composite_Component): 

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

1022 if self.typ.has_separators(): 

1023 rv = "" 

1024 else: 

1025 rv = ")" 

1026 return rv 

1027 

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

1029 assert isinstance(mh, Message_Handler) 

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

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

1032 return Value(self.location, 

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

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

1035 self.typ) 

1036 

1037 def resolve_references(self, mh): 

1038 assert isinstance(mh, Message_Handler) 

1039 

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

1041 val.resolve_references(mh) 

1042 

1043 def to_python_object(self): 

1044 return {name: value.to_python_object() 

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

1046 

1047 def can_be_null(self): 

1048 return False 

1049 

1050 

1051class Record_Reference(Expression): 

1052 """Reference to another record object 

1053 

1054 This can appear in record object declarations:: 

1055 

1056 Requirement Kitten { 

1057 depends_on = Other_Package.Cat 

1058 ^1 ^2 

1059 } 

1060 

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

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

1063 the target attribute. 

1064 

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

1066 immediately resolved on parsing in the TRLC language. 

1067 

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

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

1070 in this AST node. 

1071 

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

1073 :type: str 

1074 

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

1076 :type: Record_Object 

1077 

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

1079 :type: Package 

1080 

1081 """ 

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

1083 # lobster-exclude: Constructor only declares variables 

1084 assert isinstance(location, Location) 

1085 assert isinstance(name, str) 

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

1087 assert isinstance(package, Package) 

1088 super().__init__(location, typ) 

1089 

1090 self.name = name 

1091 self.target = None 

1092 self.package = package 

1093 

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

1095 # lobster-exclude: Debugging feature 

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

1097 self.write_indent(indent + 1, 

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

1099 

1100 def to_string(self): 

1101 return self.name 

1102 

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

1104 assert isinstance(mh, Message_Handler) 

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

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

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

1108 

1109 def resolve_references(self, mh): 

1110 # lobster-trace: LRM.References_To_Extensions 

1111 assert isinstance(mh, Message_Handler) 

1112 

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

1114 mh = mh, 

1115 name = self.name, 

1116 error_location = self.location, 

1117 required_subclass = Record_Object) 

1118 if self.typ is None: 

1119 self.typ = self.target.n_typ 

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

1121 mh.error(self.location, 

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

1123 (self.typ.name, 

1124 self.target.name, 

1125 self.target.n_typ.name)) 

1126 

1127 def to_python_object(self): 

1128 return self.target.fully_qualified_name() 

1129 

1130 def can_be_null(self): 

1131 return False 

1132 

1133 

1134class Name_Reference(Expression): 

1135 # lobster-trace: LRM.Qualified_Name 

1136 # lobster-trace: LRM.Static_Regular_Expression 

1137 

1138 """Reference to a name 

1139 

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

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

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

1143 

1144 For example:: 

1145 

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

1147 ^1 ^2 

1148 

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

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

1151 :class:`Quantified_Variable`. 

1152 

1153 :attribute entity: the entity named here 

1154 :type: Composite_Component, Quantified_Variable 

1155 """ 

1156 def __init__(self, location, entity): 

1157 assert isinstance(entity, (Composite_Component, 

1158 Quantified_Variable)) 

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

1160 self.entity = entity 

1161 

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

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

1164 

1165 def to_string(self): 

1166 return self.entity.name 

1167 

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

1169 assert isinstance(mh, Message_Handler) 

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

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

1172 

1173 if context is None: 

1174 mh.error(self.location, 

1175 "cannot be used in a static context") 

1176 

1177 assert self.entity.name in context 

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

1179 

1180 def can_be_null(self): 

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

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

1183 # optional. 

1184 if isinstance(self.entity, Composite_Component): 

1185 return self.entity.optional 

1186 else: 

1187 return False 

1188 

1189 

1190class Unary_Expression(Expression): 

1191 """Expression with only one operand 

1192 

1193 This captures the following operations: 

1194 

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

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

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

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

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

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

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

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

1203 

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

1205 

1206 :attribute operator: the operation 

1207 :type: Unary_Operator 

1208 

1209 :attribute n_operand: the expression we operate on 

1210 :type: Expression 

1211 

1212 """ 

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

1214 # lobster-trace: LRM.Simple_Expression 

1215 # lobster-trace: LRM.Relation 

1216 # lobster-trace: LRM.Factor 

1217 # lobster-trace: LRM.Signature_Len 

1218 # lobster-trace: LRM.Signature_Type_Conversion 

1219 

1220 super().__init__(location, typ) 

1221 assert isinstance(mh, Message_Handler) 

1222 assert isinstance(operator, Unary_Operator) 

1223 assert isinstance(n_operand, Expression) 

1224 self.operator = operator 

1225 self.n_operand = n_operand 

1226 

1227 if operator in (Unary_Operator.MINUS, 

1228 Unary_Operator.PLUS, 

1229 Unary_Operator.ABSOLUTE_VALUE): 

1230 self.n_operand.ensure_type(mh, Builtin_Numeric_Type) 

1231 elif operator == Unary_Operator.LOGICAL_NOT: 

1232 self.n_operand.ensure_type(mh, Builtin_Boolean) 

1233 elif operator == Unary_Operator.STRING_LENGTH: 

1234 self.n_operand.ensure_type(mh, Builtin_String) 

1235 elif operator == Unary_Operator.ARRAY_LENGTH: 

1236 self.n_operand.ensure_type(mh, Array_Type) 

1237 elif operator == Unary_Operator.CONVERSION_TO_INT: 

1238 self.n_operand.ensure_type(mh, Builtin_Numeric_Type) 

1239 elif operator == Unary_Operator.CONVERSION_TO_DECIMAL: 

1240 self.n_operand.ensure_type(mh, Builtin_Numeric_Type) 

1241 else: 

1242 mh.ice_loc(self.location, 

1243 "unexpected unary operation %s" % operator) 

1244 

1245 def to_string(self): 

1246 prefix_operators = { 

1247 Unary_Operator.MINUS : "-", 

1248 Unary_Operator.PLUS : "+", 

1249 Unary_Operator.ABSOLUTE_VALUE : "abs ", 

1250 Unary_Operator.LOGICAL_NOT : "not ", 

1251 } 

1252 function_calls = { 

1253 Unary_Operator.STRING_LENGTH : "len", 

1254 Unary_Operator.ARRAY_LENGTH : "len", 

1255 Unary_Operator.CONVERSION_TO_INT : "Integer", 

1256 Unary_Operator.CONVERSION_TO_DECIMAL : "Decimal" 

1257 } 

1258 

1259 if self.operator in prefix_operators: 

1260 return prefix_operators[self.operator] + \ 

1261 self.n_operand.to_string() 

1262 

1263 elif self.operator in function_calls: 

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

1265 self.n_operand.to_string()) 

1266 

1267 else: 

1268 assert False 

1269 

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

1271 # lobster-exclude: Debugging feature 

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

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

1274 self.n_operand.dump(indent + 1) 

1275 

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

1277 # lobster-trace: LRM.Null_Is_Invalid 

1278 # lobster-trace: LRM.Signature_Len 

1279 # lobster-trace: LRM.Signature_Type_Conversion 

1280 # lobster-trace: LRM.Len_Semantics 

1281 # lobster-trace: LRM.Integer_Conversion_Semantics 

1282 # lobster-trace: LRM.Decimal_Conversion_Semantics 

1283 

1284 assert isinstance(mh, Message_Handler) 

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

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

1287 

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

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

1290 mh.error(v_operand.location, 

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

1292 (self.to_string(), 

1293 mh.cross_file_reference(self.location))) 

1294 

1295 if self.operator == Unary_Operator.MINUS: 

1296 return Value(location = self.location, 

1297 value = -v_operand.value, 

1298 typ = self.typ) 

1299 elif self.operator == Unary_Operator.PLUS: 

1300 return Value(location = self.location, 

1301 value = +v_operand.value, 

1302 typ = self.typ) 

1303 elif self.operator == Unary_Operator.LOGICAL_NOT: 

1304 return Value(location = self.location, 

1305 value = not v_operand.value, 

1306 typ = self.typ) 

1307 elif self.operator == Unary_Operator.ABSOLUTE_VALUE: 

1308 return Value(location = self.location, 

1309 value = abs(v_operand.value), 

1310 typ = self.typ) 

1311 elif self.operator in (Unary_Operator.STRING_LENGTH, 

1312 Unary_Operator.ARRAY_LENGTH): 

1313 return Value(location = self.location, 

1314 value = len(v_operand.value), 

1315 typ = self.typ) 

1316 elif self.operator == Unary_Operator.CONVERSION_TO_INT: 

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

1318 return Value( 

1319 location = self.location, 

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

1321 typ = self.typ) 

1322 else: 

1323 return Value(location = self.location, 

1324 value = v_operand.value, 

1325 typ = self.typ) 

1326 elif self.operator == Unary_Operator.CONVERSION_TO_DECIMAL: 

1327 return Value(location = self.location, 

1328 value = Fraction(v_operand.value), 

1329 typ = self.typ) 

1330 else: 

1331 mh.ice_loc(self.location, 

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

1333 

1334 def to_python_object(self): 

1335 assert self.operator in (Unary_Operator.MINUS, 

1336 Unary_Operator.PLUS) 

1337 val = self.n_operand.to_python_object() 

1338 if self.operator == Unary_Operator.MINUS: 

1339 return -val 

1340 else: 

1341 return val 

1342 

1343 def can_be_null(self): 

1344 return False 

1345 

1346 

1347class Binary_Expression(Expression): 

1348 """Expression with two operands 

1349 

1350 This captures the following operations: 

1351 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1374 

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

1376 

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

1378 rationals and strings. 

1379 

1380 :attribute operator: the operation 

1381 :type: Binary_Operator 

1382 

1383 :attribute n_lhs: the first operand 

1384 :type: Expression 

1385 

1386 :attribute n_rhs: the second operand 

1387 :type: Expression 

1388 

1389 """ 

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

1391 # lobster-trace: LRM.Expression 

1392 # lobster-trace: LRM.Relation 

1393 # lobster-trace: LRM.Simple_Expression 

1394 # lobster-trace: LRM.Term 

1395 # lobster-trace: LRM.Factor 

1396 # lobster-trace: LRM.Signature_String_End_Functions 

1397 # lobster-trace: LRM.Signature_Matches 

1398 

1399 super().__init__(location, typ) 

1400 assert isinstance(mh, Message_Handler) 

1401 assert isinstance(operator, Binary_Operator) 

1402 assert isinstance(n_lhs, Expression) 

1403 assert isinstance(n_rhs, Expression) 

1404 self.operator = operator 

1405 self.n_lhs = n_lhs 

1406 self.n_rhs = n_rhs 

1407 

1408 if operator in (Binary_Operator.LOGICAL_AND, 

1409 Binary_Operator.LOGICAL_OR, 

1410 Binary_Operator.LOGICAL_XOR, 

1411 Binary_Operator.LOGICAL_IMPLIES): 

1412 self.n_lhs.ensure_type(mh, Builtin_Boolean) 

1413 self.n_rhs.ensure_type(mh, Builtin_Boolean) 

1414 

1415 elif operator in (Binary_Operator.COMP_EQ, 

1416 Binary_Operator.COMP_NEQ): 

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

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

1419 pass 

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

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

1422 # types match 

1423 mh.error(self.location, 

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

1425 (self.n_lhs.typ.name, 

1426 self.n_rhs.typ.name)) 

1427 

1428 elif operator in (Binary_Operator.COMP_LT, 

1429 Binary_Operator.COMP_LEQ, 

1430 Binary_Operator.COMP_GT, 

1431 Binary_Operator.COMP_GEQ): 

1432 self.n_lhs.ensure_type(mh, Builtin_Numeric_Type) 

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

1434 

1435 elif operator in (Binary_Operator.STRING_CONTAINS, 

1436 Binary_Operator.STRING_STARTSWITH, 

1437 Binary_Operator.STRING_ENDSWITH, 

1438 Binary_Operator.STRING_REGEX): 

1439 self.n_lhs.ensure_type(mh, Builtin_String) 

1440 self.n_rhs.ensure_type(mh, Builtin_String) 

1441 

1442 elif operator == Binary_Operator.ARRAY_CONTAINS: 

1443 self.n_rhs.ensure_type(mh, Array_Type) 

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

1445 

1446 elif operator == Binary_Operator.PLUS: 

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

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

1449 else: 

1450 self.n_lhs.ensure_type(mh, Builtin_String) 

1451 self.n_rhs.ensure_type(mh, Builtin_String) 

1452 

1453 elif operator in (Binary_Operator.MINUS, 

1454 Binary_Operator.TIMES, 

1455 Binary_Operator.DIVIDE): 

1456 self.n_lhs.ensure_type(mh, Builtin_Numeric_Type) 

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

1458 

1459 elif operator == Binary_Operator.POWER: 

1460 self.n_lhs.ensure_type(mh, Builtin_Numeric_Type) 

1461 self.n_rhs.ensure_type(mh, Builtin_Integer) 

1462 

1463 elif operator == Binary_Operator.REMAINDER: 

1464 self.n_lhs.ensure_type(mh, Builtin_Integer) 

1465 self.n_rhs.ensure_type(mh, Builtin_Integer) 

1466 

1467 elif operator == Binary_Operator.INDEX: 

1468 self.n_lhs.ensure_type(mh, Array_Type) 

1469 self.n_rhs.ensure_type(mh, Builtin_Integer) 

1470 

1471 else: 

1472 mh.ice_loc(self.location, 

1473 "unexpected binary operation %s" % operator) 

1474 

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

1476 # lobster-exclude: Debugging feature 

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

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

1479 self.n_lhs.dump(indent + 1) 

1480 self.n_rhs.dump(indent + 1) 

1481 

1482 def to_string(self): 

1483 infix_operators = { 

1484 Binary_Operator.LOGICAL_AND : "and", 

1485 Binary_Operator.LOGICAL_OR : "or", 

1486 Binary_Operator.LOGICAL_XOR : "xor", 

1487 Binary_Operator.LOGICAL_IMPLIES : "implies", 

1488 Binary_Operator.COMP_EQ : "==", 

1489 Binary_Operator.COMP_NEQ : "!=", 

1490 Binary_Operator.COMP_LT : "<", 

1491 Binary_Operator.COMP_LEQ : "<=", 

1492 Binary_Operator.COMP_GT : ">", 

1493 Binary_Operator.COMP_GEQ : ">=", 

1494 Binary_Operator.STRING_CONTAINS : "in", 

1495 Binary_Operator.ARRAY_CONTAINS : "in", 

1496 Binary_Operator.PLUS : "+", 

1497 Binary_Operator.MINUS : "-", 

1498 Binary_Operator.TIMES : "*", 

1499 Binary_Operator.DIVIDE : "/", 

1500 Binary_Operator.REMAINDER : "%", 

1501 Binary_Operator.POWER : "**", 

1502 } 

1503 string_functions = { 

1504 Binary_Operator.STRING_STARTSWITH : "startswith", 

1505 Binary_Operator.STRING_ENDSWITH : "endswith", 

1506 Binary_Operator.STRING_REGEX : "matches", 

1507 } 

1508 

1509 if self.operator in infix_operators: 

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

1511 infix_operators[self.operator], 

1512 self.n_rhs.to_string()) 

1513 

1514 elif self.operator in string_functions: 

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

1516 self.n_lhs.to_string(), 

1517 self.n_rhs.to_string()) 

1518 

1519 elif self.operator == Binary_Operator.INDEX: 

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

1521 self.n_rhs.to_string()) 

1522 

1523 else: 

1524 assert False 

1525 

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

1527 # lobster-trace: LRM.Null_Equivalence 

1528 # lobster-trace: LRM.Null_Is_Invalid 

1529 # lobster-trace: LRM.Signature_String_End_Functions 

1530 # lobster-trace: LRM.Signature_Matches 

1531 # lobster-trace: LRM.Startswith_Semantics 

1532 # lobster-trace: LRM.Endswith_Semantics 

1533 # lobster-trace: LRM.Matches_Semantics 

1534 

1535 assert isinstance(mh, Message_Handler) 

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

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

1538 

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

1540 if v_lhs.value is None and \ 

1541 self.operator not in (Binary_Operator.COMP_EQ, 

1542 Binary_Operator.COMP_NEQ): 

1543 mh.error(v_lhs.location, 

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

1545 (self.to_string(), 

1546 mh.cross_file_reference(self.location))) 

1547 

1548 # Check for the short-circuit operators first 

1549 if self.operator == Binary_Operator.LOGICAL_AND: 

1550 assert isinstance(v_lhs.value, bool) 

1551 if v_lhs.value: 

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

1553 else: 

1554 return v_lhs 

1555 

1556 elif self.operator == Binary_Operator.LOGICAL_OR: 

1557 assert isinstance(v_lhs.value, bool) 

1558 if v_lhs.value: 

1559 return v_lhs 

1560 else: 

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

1562 

1563 elif self.operator == Binary_Operator.LOGICAL_IMPLIES: 

1564 assert isinstance(v_lhs.value, bool) 

1565 if v_lhs.value: 

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

1567 else: 

1568 return Value(location = self.location, 

1569 value = True, 

1570 typ = self.typ) 

1571 

1572 # Otherwise, evaluate RHS and do the operation 

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

1574 if v_rhs.value is None and \ 

1575 self.operator not in (Binary_Operator.COMP_EQ, 

1576 Binary_Operator.COMP_NEQ): 

1577 mh.error(v_rhs.location, 

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

1579 (self.to_string(), 

1580 mh.cross_file_reference(self.location))) 

1581 

1582 if self.operator == Binary_Operator.LOGICAL_XOR: 

1583 assert isinstance(v_lhs.value, bool) 

1584 assert isinstance(v_rhs.value, bool) 

1585 return Value(location = self.location, 

1586 value = v_lhs.value ^ v_rhs.value, 

1587 typ = self.typ) 

1588 

1589 elif self.operator == Binary_Operator.COMP_EQ: 

1590 return Value(location = self.location, 

1591 value = v_lhs.value == v_rhs.value, 

1592 typ = self.typ) 

1593 

1594 elif self.operator == Binary_Operator.COMP_NEQ: 

1595 return Value(location = self.location, 

1596 value = v_lhs.value != v_rhs.value, 

1597 typ = self.typ) 

1598 

1599 elif self.operator in (Binary_Operator.COMP_LT, 

1600 Binary_Operator.COMP_LEQ, 

1601 Binary_Operator.COMP_GT, 

1602 Binary_Operator.COMP_GEQ): 

1603 return Value( 

1604 location = self.location, 

1605 value = { 

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

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

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

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

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

1611 typ = self.typ) 

1612 

1613 elif self.operator == Binary_Operator.STRING_CONTAINS: 

1614 assert isinstance(v_lhs.value, str) 

1615 assert isinstance(v_rhs.value, str) 

1616 

1617 return Value(location = self.location, 

1618 value = v_lhs.value in v_rhs.value, 

1619 typ = self.typ) 

1620 

1621 elif self.operator == Binary_Operator.STRING_STARTSWITH: 

1622 assert isinstance(v_lhs.value, str) 

1623 assert isinstance(v_rhs.value, str) 

1624 return Value(location = self.location, 

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

1626 typ = self.typ) 

1627 

1628 elif self.operator == Binary_Operator.STRING_ENDSWITH: 

1629 assert isinstance(v_lhs.value, str) 

1630 assert isinstance(v_rhs.value, str) 

1631 return Value(location = self.location, 

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

1633 typ = self.typ) 

1634 

1635 elif self.operator == Binary_Operator.STRING_REGEX: 

1636 assert isinstance(v_lhs.value, str) 

1637 assert isinstance(v_rhs.value, str) 

1638 return Value(location = self.location, 

1639 value = re.match(v_rhs.value, 

1640 v_lhs.value) is not None, 

1641 typ = self.typ) 

1642 

1643 elif self.operator == Binary_Operator.ARRAY_CONTAINS: 

1644 assert isinstance(v_rhs.value, list) 

1645 

1646 return Value(location = self.location, 

1647 value = v_lhs in v_rhs.value, 

1648 typ = self.typ) 

1649 

1650 elif self.operator == Binary_Operator.PLUS: 

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

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

1653 return Value(location = self.location, 

1654 value = v_lhs.value + v_rhs.value, 

1655 typ = self.typ) 

1656 

1657 elif self.operator == Binary_Operator.MINUS: 

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

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

1660 return Value(location = self.location, 

1661 value = v_lhs.value - v_rhs.value, 

1662 typ = self.typ) 

1663 

1664 elif self.operator == Binary_Operator.TIMES: 

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

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

1667 return Value(location = self.location, 

1668 value = v_lhs.value * v_rhs.value, 

1669 typ = self.typ) 

1670 

1671 elif self.operator == Binary_Operator.DIVIDE: 

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

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

1674 

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

1676 mh.error(v_rhs.location, 

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

1678 (self.to_string(), 

1679 mh.cross_file_reference(self.location))) 

1680 

1681 if isinstance(v_lhs.value, int): 

1682 return Value(location = self.location, 

1683 value = v_lhs.value // v_rhs.value, 

1684 typ = self.typ) 

1685 else: 

1686 return Value(location = self.location, 

1687 value = v_lhs.value / v_rhs.value, 

1688 typ = self.typ) 

1689 

1690 elif self.operator == Binary_Operator.REMAINDER: 

1691 assert isinstance(v_lhs.value, int) 

1692 assert isinstance(v_rhs.value, int) 

1693 

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

1695 mh.error(v_rhs.location, 

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

1697 (self.to_string(), 

1698 mh.cross_file_reference(self.location))) 

1699 

1700 return Value(location = self.location, 

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

1702 typ = self.typ) 

1703 

1704 elif self.operator == Binary_Operator.POWER: 

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

1706 assert isinstance(v_rhs.value, int) 

1707 return Value(location = self.location, 

1708 value = v_lhs.value ** v_rhs.value, 

1709 typ = self.typ) 

1710 

1711 elif self.operator == Binary_Operator.INDEX: 

1712 assert isinstance(v_lhs.value, list) 

1713 assert isinstance(v_rhs.value, int) 

1714 

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

1716 mh.error(v_rhs.location, 

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

1718 (self.to_string(), 

1719 mh.cross_file_reference(self.location))) 

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

1721 v_rhs.value > v_lhs.typ.upper_bound: 

1722 mh.error(v_rhs.location, 

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

1724 (v_lhs.typ.upper_bound, 

1725 self.to_string(), 

1726 mh.cross_file_reference(self.location))) 

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

1728 mh.error(v_lhs.location, 

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

1730 (self.to_string(), 

1731 mh.cross_file_reference(self.location))) 

1732 

1733 return Value(location = self.location, 

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

1735 typ = self.typ) 

1736 

1737 else: 

1738 mh.ice_loc(self.location, 

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

1740 

1741 def can_be_null(self): 

1742 return False 

1743 

1744 

1745class Field_Access_Expression(Expression): 

1746 """Tuple or Record field access 

1747 

1748 For example in:: 

1749 

1750 foo.bar 

1751 ^1 ^2 

1752 

1753 :attribute n_prefix: expression with tuple or record type (see 1) 

1754 :type: Expression 

1755 

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

1757 :type: Composite_Component 

1758 

1759 """ 

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

1761 assert isinstance(mh, Message_Handler) 

1762 assert isinstance(n_prefix, Expression) 

1763 assert isinstance(n_field, Composite_Component) 

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

1765 self.n_prefix = n_prefix 

1766 self.n_field = n_field 

1767 

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

1769 

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

1771 # lobster-exclude: Debugging feature 

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

1773 self.n_prefix.dump(indent + 1) 

1774 

1775 def to_string(self): 

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

1777 

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

1779 assert isinstance(mh, Message_Handler) 

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

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

1782 

1783 v_prefix = self.n_prefix.evaluate(mh, 

1784 context, 

1785 gstab).value 

1786 if v_prefix is None: 

1787 # lobster-trace: LRM.Dereference 

1788 mh.error(self.n_prefix.location, 

1789 "null dereference") 

1790 

1791 v_field = v_prefix[self.n_field.name] 

1792 if isinstance(v_field, Implicit_Null): 

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

1794 else: 

1795 return v_field 

1796 

1797 def can_be_null(self): 

1798 return False 

1799 

1800 

1801class Range_Test(Expression): 

1802 """Range membership test 

1803 

1804 For example in:: 

1805 

1806 x in 1 .. field+1 

1807 ^lhs ^lower ^^^^^^^upper 

1808 

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

1810 you can have arbitrarily complex expressions here. 

1811 

1812 :attribute n_lhs: the expression to test 

1813 :type: Expression 

1814 

1815 :attribute n_lower: the lower bound 

1816 :type: Expression 

1817 

1818 :attribute n_upper: the upper bound 

1819 :type: Expression 

1820 

1821 """ 

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

1823 # lobster-trace: LRM.Relation 

1824 super().__init__(location, typ) 

1825 assert isinstance(mh, Message_Handler) 

1826 assert isinstance(n_lhs, Expression) 

1827 assert isinstance(n_lower, Expression) 

1828 assert isinstance(n_upper, Expression) 

1829 self.n_lhs = n_lhs 

1830 self.n_lower = n_lower 

1831 self.n_upper = n_upper 

1832 

1833 self.n_lhs.ensure_type(mh, Builtin_Numeric_Type) 

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

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

1836 

1837 def to_string(self): 

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

1839 self.n_lower.to_string(), 

1840 self.n_upper.to_string()) 

1841 

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

1843 # lobster-exclude: Debugging feature 

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

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

1846 self.n_lhs.dump(indent + 1) 

1847 self.n_lower.dump(indent + 1) 

1848 self.n_upper.dump(indent + 1) 

1849 

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

1851 # lobster-trace: LRM.Null_Is_Invalid 

1852 assert isinstance(mh, Message_Handler) 

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

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

1855 

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

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

1858 mh.error(v_lhs.location, 

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

1860 (self.to_string(), 

1861 mh.cross_file_reference(self.location))) 

1862 

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

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

1865 mh.error(v_lower.location, 

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

1867 (self.to_string(), 

1868 mh.cross_file_reference(self.location))) 

1869 

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

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

1872 mh.error(v_upper.location, 

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

1874 (self.to_string(), 

1875 mh.cross_file_reference(self.location))) 

1876 

1877 return Value(location = self.location, 

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

1879 typ = self.typ) 

1880 

1881 def can_be_null(self): 

1882 return False 

1883 

1884 

1885class OneOf_Expression(Expression): 

1886 """OneOf expression 

1887 

1888 For example in:: 

1889 

1890 oneof(a, b, c) 

1891 ^^^^^^^ choices 

1892 

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

1894 :type: list[Expression] 

1895 """ 

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

1897 # lobster-trace: LRM.Signature_OneOf 

1898 super().__init__(location, typ) 

1899 assert isinstance(typ, Builtin_Boolean) 

1900 assert isinstance(mh, Message_Handler) 

1901 assert isinstance(choices, list) 

1902 assert all(isinstance(item, Expression) 

1903 for item in choices) 

1904 self.choices = choices 

1905 

1906 for n_choice in choices: 

1907 n_choice.ensure_type(mh, Builtin_Boolean) 

1908 

1909 def to_string(self): 

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

1911 for n_choice in self.choices) 

1912 

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

1914 # lobster-exclude: Debugging feature 

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

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

1917 for n_choice in self.choices: 

1918 n_choice.dump(indent + 1) 

1919 

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

1921 # lobster-trace: LRM.OneOf_Semantics 

1922 assert isinstance(mh, Message_Handler) 

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

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

1925 

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

1927 for n_choice in self.choices] 

1928 

1929 return Value(location = self.location, 

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

1931 typ = self.typ) 

1932 

1933 def can_be_null(self): 

1934 return False 

1935 

1936 

1937class Action(Node): 

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

1939 

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

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

1942 Actions:: 

1943 

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

1945 ^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^ 

1946 

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

1948 the :class:`Conditional_Expression` itself. 

1949 

1950 :attribute kind: Either if or elseif 

1951 :type: str 

1952 

1953 :attribute n_cond: The boolean condition expression 

1954 :type: Expression 

1955 

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

1957 :type: Expression 

1958 

1959 """ 

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

1961 # lobster-trace: LRM.Conditional_Expression 

1962 assert isinstance(mh, Message_Handler) 

1963 assert isinstance(t_kind, Token) 

1964 assert t_kind.kind == "KEYWORD" 

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

1966 assert isinstance(n_condition, Expression) 

1967 assert isinstance(n_expression, Expression) 

1968 super().__init__(t_kind.location) 

1969 self.kind = t_kind.value 

1970 self.n_cond = n_condition 

1971 self.n_expr = n_expression 

1972 # lobster-trace: LRM.Conditional_Expression_Types 

1973 self.n_cond.ensure_type(mh, Builtin_Boolean) 

1974 

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

1976 # lobster-exclude: Debugging feature 

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

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

1979 self.n_cond.dump(indent + 2) 

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

1981 self.n_expr.dump(indent + 2) 

1982 

1983 def to_string(self): 

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

1985 self.n_cond.to_string(), 

1986 self.n_expr.to_string()) 

1987 

1988 

1989class Conditional_Expression(Expression): 

1990 """A conditional expression 

1991 

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

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

1994 expression with two Actions:: 

1995 

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

1997 ^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^ 

1998 

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

2000 

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

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

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

2004 of all actions and the else expression will match. 

2005 

2006 :attribute actions: a list of Actions 

2007 :type: list[Action] 

2008 

2009 :attribute else_expr: the else expression 

2010 :type: Expression 

2011 

2012 """ 

2013 def __init__(self, location, if_action): 

2014 # lobster-trace: LRM.Conditional_Expression 

2015 assert isinstance(if_action, Action) 

2016 assert if_action.kind == "if" 

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

2018 self.actions = [if_action] 

2019 self.else_expr = None 

2020 

2021 def add_elsif(self, mh, n_action): 

2022 # lobster-trace: LRM.Conditional_Expression 

2023 # lobster-trace; LRM.Conditional_Expression_Types 

2024 assert isinstance(mh, Message_Handler) 

2025 assert isinstance(n_action, Action) 

2026 assert n_action.kind == "elsif" 

2027 

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

2029 self.actions.append(n_action) 

2030 

2031 def set_else_part(self, mh, n_expr): 

2032 # lobster-trace: LRM.Conditional_Expression 

2033 # lobster-trace; LRM.Conditional_Expression_Types 

2034 assert isinstance(mh, Message_Handler) 

2035 assert isinstance(n_expr, Expression) 

2036 

2037 n_expr.ensure_type(mh, self.typ) 

2038 self.else_expr = n_expr 

2039 

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

2041 # lobster-exclude: Debugging feature 

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

2043 for action in self.actions: 

2044 action.dump(indent + 1) 

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

2046 self.else_expr.dump(indent + 2) 

2047 

2048 def to_string(self): 

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

2050 for action in self.actions) 

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

2052 rv += ")" 

2053 return rv 

2054 

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

2056 # lobster-trace: LRM.Conditional_Expression_Else 

2057 # lobster-trace: LRM.Conditional_Expression_Evaluation 

2058 # lobster-trace: LRM.Null_Is_Invalid 

2059 assert isinstance(mh, Message_Handler) 

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

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

2062 

2063 for action in self.actions: 

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

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

2066 mh.error(v_cond.location, 

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

2068 (action.to_string(), 

2069 mh.cross_file_reference(self.location))) 

2070 if v_cond.value: 

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

2072 

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

2074 

2075 def can_be_null(self): 

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

2077 return True 

2078 

2079 return any(action.n_expr.can_be_null() 

2080 for action in self.actions) 

2081 

2082 

2083class Quantified_Expression(Expression): 

2084 """A quantified expression 

2085 

2086 For example:: 

2087 

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

2089 ^4 ^1 ^2 ^^^^^3 

2090 

2091 A quantified expression introduces and binds a 

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

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

2094 each component of the source in turn. 

2095 

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

2097 :type: Quantified_Variable 

2098 

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

2100 :type: Name_Reference 

2101 

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

2103 :type: Expression 

2104 

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

2106 :type: Boolean 

2107 

2108 """ 

2109 def __init__(self, mh, location, 

2110 typ, 

2111 universal, 

2112 n_variable, 

2113 n_source, 

2114 n_expr): 

2115 # lobster-trace: LRM.Quantified_Expression 

2116 # lobster-trace: LRM.Quantification_Type 

2117 super().__init__(location, typ) 

2118 assert isinstance(typ, Builtin_Boolean) 

2119 assert isinstance(universal, bool) 

2120 assert isinstance(n_variable, Quantified_Variable) 

2121 assert isinstance(n_expr, Expression) 

2122 assert isinstance(n_source, Name_Reference) 

2123 self.universal = universal 

2124 self.n_var = n_variable 

2125 self.n_expr = n_expr 

2126 self.n_source = n_source 

2127 self.n_expr.ensure_type(mh, Builtin_Boolean) 

2128 

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

2130 # lobster-exclude: Debugging feature 

2131 if self.universal: 

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

2133 else: 

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

2135 self.n_var.dump(indent + 1) 

2136 self.n_expr.dump(indent + 1) 

2137 

2138 def to_string(self): 

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

2140 if self.universal 

2141 else "exists", 

2142 self.n_var.name, 

2143 self.n_source.to_string(), 

2144 self.n_expr.to_string()) 

2145 

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

2147 # lobster-trace: LRM.Null_Is_Invalid 

2148 # lobster-trace: LRM.Universal_Quantification_Semantics 

2149 # lobster-trace: LRM.Existential_Quantification_Semantics 

2150 assert isinstance(mh, Message_Handler) 

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

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

2153 

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

2155 new_ctx = {} 

2156 else: 

2157 new_ctx = copy(context) 

2158 

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

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

2161 # error messages. 

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

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

2164 if isinstance(array_values, Implicit_Null): 

2165 mh.error(array_values.location, 

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

2167 "must not be null" % 

2168 (self.n_source.to_string(), 

2169 self.to_string(), 

2170 mh.cross_file_reference(self.location))) 

2171 else: 

2172 assert isinstance(array_values, Array_Aggregate) 

2173 

2174 rv = self.universal 

2175 loc = self.location 

2176 for binding in array_values.value: 

2177 new_ctx[self.n_var.name] = binding 

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

2179 assert isinstance(result.value, bool) 

2180 if self.universal and not result.value: 

2181 rv = False 

2182 loc = binding.location 

2183 break 

2184 elif not self.universal and result.value: 

2185 rv = True 

2186 loc = binding.location 

2187 break 

2188 

2189 return Value(location = loc, 

2190 value = rv, 

2191 typ = self.typ) 

2192 

2193 def can_be_null(self): 

2194 return False 

2195 

2196 

2197############################################################################## 

2198# AST Nodes (Entities) 

2199############################################################################## 

2200 

2201class Entity(Node, metaclass=ABCMeta): 

2202 """Base class for all entities. 

2203 

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

2205 allocate memory. Examples of entities are types and record 

2206 objects. 

2207 

2208 :attribute name: unqualified name of the entity 

2209 :type: str 

2210 

2211 """ 

2212 def __init__(self, name, location): 

2213 # lobster-trace: LRM.Described_Name_Equality 

2214 super().__init__(location) 

2215 assert isinstance(name, str) 

2216 self.name = name 

2217 

2218 

2219class Typed_Entity(Entity, metaclass=ABCMeta): 

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

2221 

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

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

2224 are record objects and components. 

2225 

2226 :attribute n_typ: type of the entity 

2227 :type: Type 

2228 

2229 """ 

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

2231 # lobster-exclude: Constructor only declares variables 

2232 super().__init__(name, location) 

2233 assert isinstance(n_typ, Type) 

2234 self.n_typ = n_typ 

2235 

2236 

2237class Quantified_Variable(Typed_Entity): 

2238 """Variable used in quantified expression. 

2239 

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

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

2242 

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

2244 ^ 

2245 

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

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

2248 

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

2250 :type: Type 

2251 

2252 """ 

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

2254 # lobster-exclude: Debugging feature 

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

2256 self.n_typ.dump(indent + 1) 

2257 

2258 

2259class Type(Entity, metaclass=ABCMeta): 

2260 """Abstract base class for all types. 

2261 

2262 """ 

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

2264 assert isinstance(mh, Message_Handler) 

2265 assert isinstance(value, Expression) 

2266 assert isinstance(gstab, Symbol_Table) 

2267 return True 

2268 

2269 def get_example_value(self): 

2270 # lobster-exclude: utility method 

2271 assert False 

2272 

2273 

2274class Concrete_Type(Type, metaclass=ABCMeta): 

2275 # lobster-trace: LRM.Type_Declarations 

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

2277 

2278 :attribute n_package: package where this type was declared 

2279 :type: Package 

2280 """ 

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

2282 super().__init__(name, location) 

2283 assert isinstance(n_package, Package) 

2284 self.n_package = n_package 

2285 

2286 def fully_qualified_name(self): 

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

2288 

2289 :returns: the type's full name 

2290 :rtype: str 

2291 """ 

2292 return self.n_package.name + "." + self.name 

2293 

2294 def __hash__(self): 

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

2296 

2297 def __repr__(self): 

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

2299 self.fully_qualified_name()) 

2300 

2301 

2302class Builtin_Type(Type, metaclass=ABCMeta): 

2303 # lobster-trace: LRM.Builtin_Types 

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

2305 

2306 """ 

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

2308 

2309 def __init__(self, name): 

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

2311 

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

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

2314 

2315 

2316class Builtin_Numeric_Type(Builtin_Type, metaclass=ABCMeta): 

2317 # lobster-trace: LRM.Builtin_Types 

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

2319 

2320 """ 

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

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

2323 

2324 

2325class Builtin_Function(Entity): 

2326 # lobster-trace: LRM.Builtin_Functions 

2327 """Builtin functions. 

2328 

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

2330 

2331 :attribute arity: number of parameters 

2332 :type: int 

2333 

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

2335 :type: bool 

2336 

2337 """ 

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

2339 

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

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

2342 assert isinstance(arity, int) 

2343 assert isinstance(arity_at_least, bool) 

2344 assert arity >= 0 

2345 self.arity = arity 

2346 self.arity_at_least = arity_at_least 

2347 

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

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

2350 

2351 

2352class Array_Type(Type): 

2353 """Anonymous array type. 

2354 

2355 These are declared implicitly for each record component that has 

2356 an array specifier:: 

2357 

2358 foo Integer [5 .. *] 

2359 ^ 

2360 

2361 :attribute lower_bound: minimum number of elements 

2362 :type: int 

2363 

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

2365 :type: Location 

2366 

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

2368 :type: int 

2369 

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

2371 :type: Location 

2372 

2373 :attribute element_type: type of the array elements 

2374 :type: Type 

2375 

2376 """ 

2377 def __init__(self, 

2378 location, 

2379 element_type, 

2380 loc_lower, 

2381 lower_bound, 

2382 loc_upper, 

2383 upper_bound): 

2384 # lobster-exclude: Constructor only declares variables 

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

2386 assert isinstance(lower_bound, int) 

2387 assert lower_bound >= 0 

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

2389 assert upper_bound is None or upper_bound >= 0 

2390 assert isinstance(loc_lower, Location) 

2391 assert isinstance(loc_upper, Location) 

2392 

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

2394 name = "universal array" 

2395 elif upper_bound is None: 

2396 if lower_bound == 0: 

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

2398 else: 

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

2400 element_type.name) 

2401 elif lower_bound == upper_bound: 

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

2403 element_type.name) 

2404 else: 

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

2406 upper_bound, 

2407 element_type.name) 

2408 super().__init__(name, location) 

2409 self.lower_bound = lower_bound 

2410 self.loc_lower = loc_lower 

2411 self.upper_bound = upper_bound 

2412 self.loc_upper = loc_upper 

2413 self.element_type = element_type 

2414 

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

2416 # lobster-exclude: Debugging feature 

2417 self.write_indent(indent, "Array_Type") 

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

2419 if self.upper_bound is None: 

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

2421 else: 

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

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

2424 

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

2426 assert isinstance(mh, Message_Handler) 

2427 assert isinstance(gstab, Symbol_Table) 

2428 

2429 if isinstance(value, Array_Aggregate): 

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

2431 for v in value.value) 

2432 else: 

2433 assert isinstance(value, Implicit_Null) 

2434 return True 

2435 

2436 def get_example_value(self): 

2437 # lobster-exclude: utility method 

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

2439 

2440 

2441class Builtin_Integer(Builtin_Numeric_Type): 

2442 # lobster-trace: LRM.Builtin_Types 

2443 # lobster-trace: LRM.Integer_Values 

2444 """Builtin integer type.""" 

2445 def __init__(self): 

2446 super().__init__("Integer") 

2447 

2448 def get_example_value(self): 

2449 # lobster-exclude: utility method 

2450 return "100" 

2451 

2452 

2453class Builtin_Decimal(Builtin_Numeric_Type): 

2454 # lobster-trace: LRM.Builtin_Types 

2455 # lobster-trace: LRM.Decimal_Values 

2456 """Builtin decimal type.""" 

2457 def __init__(self): 

2458 super().__init__("Decimal") 

2459 

2460 def get_example_value(self): 

2461 # lobster-exclude: utility method 

2462 return "3.14" 

2463 

2464 

2465class Builtin_Boolean(Builtin_Type): 

2466 # lobster-trace: LRM.Builtin_Types 

2467 # lobster-trace: LRM.Boolean_Values 

2468 """Builtin boolean type.""" 

2469 def __init__(self): 

2470 super().__init__("Boolean") 

2471 

2472 def get_example_value(self): 

2473 # lobster-exclude: utility method 

2474 return "true" 

2475 

2476 

2477class Builtin_String(Builtin_Type): 

2478 # lobster-trace: LRM.Builtin_Types 

2479 # lobster-trace: LRM.String_Values 

2480 """Builtin string type.""" 

2481 def __init__(self): 

2482 super().__init__("String") 

2483 

2484 def get_example_value(self): 

2485 # lobster-exclude: utility method 

2486 return "\"potato\"" 

2487 

2488 

2489class Builtin_Markup_String(Builtin_String): 

2490 # lobster-trace: LRM.Builtin_Types 

2491 # lobster-trace: LRM.Markup_String_Values 

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

2493 objects. 

2494 """ 

2495 def __init__(self): 

2496 super().__init__() 

2497 self.name = "Markup_String" 

2498 

2499 def get_example_value(self): 

2500 # lobster-exclude: utility method 

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

2502 

2503 

2504class Package(Entity): 

2505 """Packages. 

2506 

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

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

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

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

2511 

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

2513 trlc file 

2514 :type: bool 

2515 

2516 :attribute symbols: symbol table of the package 

2517 :type: Symbol_Table 

2518 

2519 """ 

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

2521 # lobster-exclude: Constructor only declares variables 

2522 super().__init__(name, location) 

2523 assert isinstance(builtin_stab, Symbol_Table) 

2524 assert isinstance(declared_late, bool) 

2525 self.symbols = Symbol_Table() 

2526 self.symbols.make_visible(builtin_stab) 

2527 self.declared_late = declared_late 

2528 

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

2530 # lobster-exclude: Debugging feature 

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

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

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

2534 

2535 def __repr__(self): 

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

2537 self.name) 

2538 

2539 

2540class Composite_Type(Concrete_Type, metaclass=ABCMeta): 

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

2542 functionality. 

2543 

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

2545 :type: Symbol_Table[Composite_Component] 

2546 

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

2548 :type: str 

2549 

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

2551 inherited checks) 

2552 :type: list[Check] 

2553 

2554 """ 

2555 def __init__(self, 

2556 name, 

2557 description, 

2558 location, 

2559 package, 

2560 inherited_symbols=None): 

2561 # lobster-trace: LRM.Described_Name_Description 

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

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

2564 assert isinstance(inherited_symbols, Symbol_Table) or \ 

2565 inherited_symbols is None 

2566 

2567 self.components = Symbol_Table(inherited_symbols) 

2568 self.description = description 

2569 self.checks = [] 

2570 

2571 def add_check(self, n_check): 

2572 # lobster-trace: LRM.Check_Evaluation_Order 

2573 assert isinstance(n_check, Check) 

2574 self.checks.append(n_check) 

2575 

2576 def iter_checks(self): 

2577 # lobster-trace: LRM.Check_Evaluation_Order 

2578 yield from self.checks 

2579 

2580 def all_components(self): 

2581 # lobster-exclude: Convenience function 

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

2583 

2584 :rtype: list[Composite_Component] 

2585 """ 

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

2587 

2588 

2589class Composite_Component(Typed_Entity): 

2590 """Component in a record or tuple. 

2591 

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

2593 declared:: 

2594 

2595 type|tuple T { 

2596 foo "blah" optional Boolean 

2597 ^1 ^2 ^3 ^4 

2598 

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

2600 :type: str 

2601 

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

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

2604 :type: Composite_Type 

2605 

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

2607 :type: bool 

2608 

2609 """ 

2610 

2611 def __init__(self, 

2612 name, 

2613 description, 

2614 location, 

2615 member_of, 

2616 n_typ, 

2617 optional): 

2618 # lobster-trace: LRM.Described_Name_Description 

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

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

2621 assert isinstance(member_of, Composite_Type) 

2622 assert isinstance(optional, bool) 

2623 self.description = description 

2624 self.member_of = member_of 

2625 self.optional = optional 

2626 

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

2628 # lobster-exclude: Debugging feature 

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

2630 if self.description: 

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

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

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

2634 

2635 def __repr__(self): 

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

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

2638 self.name) 

2639 

2640 

2641class Record_Type(Composite_Type): 

2642 """A user-defined record type. 

2643 

2644 In this example:: 

2645 

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

2647 ^1 ^2 ^3 

2648 

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

2650 of the :class:`Composite_Type` base. 

2651 

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

2653 :type: Record_Type 

2654 

2655 :attribute frozen: mapping of frozen components 

2656 :type: dict[str, Expression] 

2657 

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

2659 :type: bool 

2660 

2661 :attribute is_abstract: type is abstract 

2662 :type: bool 

2663 

2664 """ 

2665 def __init__(self, 

2666 name, 

2667 description, 

2668 location, 

2669 package, 

2670 n_parent, 

2671 is_abstract): 

2672 # lobster-exclude: Constructor only declares variables 

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

2674 assert isinstance(is_abstract, bool) 

2675 super().__init__(name, 

2676 description, 

2677 location, 

2678 package, 

2679 n_parent.components if n_parent else None) 

2680 self.parent = n_parent 

2681 self.frozen = {} 

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

2683 self.is_abstract = is_abstract 

2684 

2685 def iter_checks(self): 

2686 # lobster-trace: LRM.Check_Evaluation_Order 

2687 # lobster-trace: LRM.Check_Evaluation_Order_For_Extensions 

2688 if self.parent: 

2689 yield from self.parent.iter_checks() 

2690 yield from self.checks 

2691 

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

2693 # lobster-exclude: Debugging feature 

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

2695 if self.description: 

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

2697 if self.parent: 

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

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

2700 if self.checks: 

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

2702 for n_check in self.checks: 

2703 n_check.dump(indent + 2) 

2704 else: 

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

2706 

2707 def all_components(self): 

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

2709 

2710 :rtype: list[Composite_Component] 

2711 """ 

2712 if self.parent: 

2713 return self.parent.all_components() + \ 

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

2715 else: 

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

2717 

2718 def is_subclass_of(self, record_type): 

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

2720 

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

2722 :type record_type: Record_Type 

2723 

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

2725 :rtype: Boolean 

2726 """ 

2727 assert isinstance(record_type, Record_Type) 

2728 

2729 ptr = self 

2730 while ptr: 

2731 if ptr is record_type: 

2732 return True 

2733 else: 

2734 ptr = ptr.parent 

2735 return False 

2736 

2737 def is_frozen(self, n_component): 

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

2739 

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

2741 (or any of its parents) 

2742 :type n_component: Composite_Component 

2743 

2744 :rtype: bool 

2745 """ 

2746 assert isinstance(n_component, Composite_Component) 

2747 if n_component.name in self.frozen: 

2748 return True 

2749 elif self.parent: 

2750 return self.parent.is_frozen(n_component) 

2751 else: 

2752 return False 

2753 

2754 def get_freezing_expression(self, n_component): 

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

2756 

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

2758 component that his not frozen. 

2759 

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

2761 (or any of its parents) 

2762 :type n_component: Composite_Component 

2763 

2764 :rtype: Expression 

2765 

2766 """ 

2767 assert isinstance(n_component, Composite_Component) 

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

2769 return self.frozen[n_component.name] 

2770 elif self.parent: 

2771 return self.parent.get_freezing_expression(n_component) 

2772 else: 

2773 assert False 

2774 

2775 def get_example_value(self): 

2776 # lobster-exclude: utility method 

2777 return "%s_instance" % self.name 

2778 

2779 

2780class Tuple_Type(Composite_Type): 

2781 """A user-defined tuple type. 

2782 

2783 In this example:: 

2784 

2785 tuple T "optional description of T" { 

2786 ^1 ^2 

2787 

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

2789 of the :class:`Composite_Type` base. 

2790 

2791 :attribute separators: list of syntactic separators. 

2792 :type: list[Separator] 

2793 

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

2795 precisely one less separator than components. 

2796 

2797 """ 

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

2799 # lobster-trace: LRM.Tuple_Declaration 

2800 super().__init__(name, 

2801 description, 

2802 location, 

2803 package) 

2804 self.separators = [] 

2805 

2806 def add_separator(self, n_separator): 

2807 # lobster-exclude: utility method 

2808 assert isinstance(n_separator, Separator) 

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

2810 self.separators.append(n_separator) 

2811 

2812 def iter_separators(self): 

2813 """Iterate over all separators""" 

2814 # lobster-exclude: utility method 

2815 yield from self.separators 

2816 

2817 def iter_sequence(self): 

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

2819 # lobster-exclude: utility method 

2820 if self.separators: 

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

2822 yield n_component 

2823 if i < len(self.separators): 

2824 yield self.separators[i] 

2825 else: 

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

2827 

2828 def has_separators(self): 

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

2830 # lobster-exclude: utility method 

2831 return bool(self.separators) 

2832 

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

2834 # lobster-exclude: Debugging feature 

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

2836 if self.description: 

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

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

2839 for n_item in self.iter_sequence(): 

2840 n_item.dump(indent + 2) 

2841 if self.checks: 

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

2843 for n_check in self.checks: 

2844 n_check.dump(indent + 2) 

2845 else: 

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

2847 

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

2849 # lobster-trace: LRM.Check_Evaluation_Order 

2850 assert isinstance(mh, Message_Handler) 

2851 assert isinstance(gstab, Symbol_Table) 

2852 

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

2854 ok = True 

2855 for check in self.iter_checks(): 

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

2857 ok = False 

2858 return ok 

2859 else: 

2860 assert isinstance(value, Implicit_Null) 

2861 return True 

2862 

2863 def get_example_value(self): 

2864 # lobster-exclude: utility method 

2865 parts = [] 

2866 for n_item in self.iter_sequence(): 

2867 if isinstance(n_item, Composite_Component): 

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

2869 else: 

2870 parts.append(n_item.to_string()) 

2871 if self.has_separators(): 

2872 return " ".join(parts) 

2873 else: 

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

2875 

2876 

2877class Separator(Node): 

2878 # lobster-trace: LRM.Tuple_Declaration 

2879 """User-defined syntactic separator 

2880 

2881 For example:: 

2882 

2883 separator x 

2884 ^1 

2885 

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

2887 :type: Token 

2888 """ 

2889 def __init__(self, token): 

2890 super().__init__(token.location) 

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

2892 "AT", 

2893 "COLON", 

2894 "SEMICOLON") 

2895 self.token = token 

2896 

2897 def to_string(self): 

2898 return { 

2899 "AT" : "@", 

2900 "COLON" : ":", 

2901 "SEMICOLON" : ";" 

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

2903 

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

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

2906 

2907 

2908class Enumeration_Type(Concrete_Type): 

2909 """User-defined enumeration types. 

2910 

2911 For example:: 

2912 

2913 enum T "potato" { 

2914 ^1 ^2 

2915 

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

2917 :type: str 

2918 

2919 :attribute literals: the literals in this enumeration 

2920 :type: Symbol_Table[Enumeration_Literal_Spec] 

2921 

2922 """ 

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

2924 # lobster-trace: LRM.Described_Name_Description 

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

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

2927 self.literals = Symbol_Table() 

2928 self.description = description 

2929 

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

2931 # lobster-exclude: Debugging feature 

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

2933 if self.description: 

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

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

2936 

2937 def get_example_value(self): 

2938 # lobster-exclude: utility method 

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

2940 if options: 

2941 choice = len(options) // 2 

2942 return self.name + "." + choice.name 

2943 else: 

2944 return "ERROR" 

2945 

2946 

2947class Enumeration_Literal_Spec(Typed_Entity): 

2948 """Declared literal in an enumeration declaration. 

2949 

2950 Note that for literals mentioned later in record object 

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

2952 are used here:: 

2953 

2954 enum ASIL { 

2955 QM "not safety related" 

2956 ^1 ^2 

2957 

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

2959 :type: str 

2960 

2961 """ 

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

2963 # lobster-trace: LRM.Described_Name_Description 

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

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

2966 assert isinstance(enum, Enumeration_Type) 

2967 self.description = description 

2968 

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

2970 # lobster-exclude: Debugging feature 

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

2972 if self.description: 

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

2974 

2975 

2976class Record_Object(Typed_Entity): 

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

2978 

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

2980 

2981 section "Potato" { 

2982 ^5 

2983 Requirement PotatoReq { 

2984 ^1 ^2 

2985 component1 = 42 

2986 ^3 ^4 

2987 

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

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

2990 class. 

2991 

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

2993 :type: dict[str, Expression] 

2994 

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

2996 :type: Section 

2997 

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

2999 :type: Section 

3000 

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

3002 to: 

3003 

3004 * :class:`Literal` 

3005 * :class:`Unary_Expression` 

3006 * :class:`Array_Aggregate` 

3007 * :class:`Tuple_Aggregate` 

3008 * :class:`Record_Reference` 

3009 * :class:`Implicit_Null` 

3010 

3011 """ 

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

3013 # lobster-trace: LRM.Section_Declaration 

3014 # lobster-trace: LRM.Unspecified_Optional_Components 

3015 # lobster-trace: LRM.Record_Object_Declaration 

3016 

3017 assert isinstance(n_typ, Record_Type) 

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

3019 assert isinstance(n_package, Package) 

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

3021 self.field = { 

3022 comp.name: Implicit_Null(self, comp) 

3023 for comp in self.n_typ.all_components() 

3024 } 

3025 self.section = section 

3026 self.n_package = n_package 

3027 

3028 def fully_qualified_name(self): 

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

3030 

3031 :returns: the object's full name 

3032 :rtype: str 

3033 """ 

3034 return self.n_package.name + "." + self.name 

3035 

3036 def to_python_dict(self): 

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

3038 

3039 For example it might provide:: 

3040 

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

3042 "bar" : None, 

3043 "baz" : "value"} 

3044 

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

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

3047 

3048 """ 

3049 return {name: value.to_python_object() 

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

3051 

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

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

3054 

3055 def assign(self, component, value): 

3056 assert isinstance(component, Composite_Component) 

3057 assert isinstance(value, (Literal, 

3058 Array_Aggregate, 

3059 Tuple_Aggregate, 

3060 Record_Reference, 

3061 Implicit_Null, 

3062 Unary_Expression)), \ 

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

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

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

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

3067 self.field[component.name] = value 

3068 

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

3070 # lobster-exclude: Debugging feature 

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

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

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

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

3075 value.dump(indent + 2) 

3076 if self.section: 

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

3078 

3079 def resolve_references(self, mh): 

3080 assert isinstance(mh, Message_Handler) 

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

3082 val.resolve_references(mh) 

3083 

3084 def perform_checks(self, mh, gstab): 

3085 # lobster-trace: LRM.Check_Evaluation_Order 

3086 # lobster-trace: LRM.Evaluation_Of_Checks 

3087 assert isinstance(mh, Message_Handler) 

3088 assert isinstance(gstab, Symbol_Table) 

3089 

3090 ok = True 

3091 

3092 # First evaluate all tuple checks 

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

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

3095 self.field[n_comp.name], 

3096 gstab): 

3097 ok = False 

3098 

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

3100 

3101 # Then evaluate all record checks 

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

3103 # Prints messages, if applicable. Raises exception on 

3104 # fatal checks, which causes this to abort. 

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

3106 ok = False 

3107 

3108 return ok 

3109 

3110 def __repr__(self): 

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

3112 self.n_package.name + "." + 

3113 self.n_typ.name + "." + 

3114 self.name) 

3115 

3116 

3117class Section(Entity): 

3118 # lobster-trace: LRM.Section_Declaration 

3119 """A section for readability 

3120 

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

3122 objects together:: 

3123 

3124 section "Foo" { 

3125 ^^^^^ parent section 

3126 section "Bar" { 

3127 ^^^^^ section 

3128 

3129 :attribute parent: the parent section or None 

3130 :type: Section 

3131 

3132 """ 

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

3134 super().__init__(name, location) 

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

3136 self.parent = parent 

3137 

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

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

3140 if self.parent is None: 

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

3142 else: 

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

3144 

3145 

3146############################################################################## 

3147# Symbol Table & Scopes 

3148############################################################################## 

3149 

3150class Symbol_Table: 

3151 """ Symbol table mapping names to entities 

3152 """ 

3153 def __init__(self, parent=None): 

3154 # lobster-exclude: Constructor only declares variables 

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

3156 self.parent = parent 

3157 self.imported = [] 

3158 self.table = OrderedDict() 

3159 self.trlc_files = [] 

3160 self.section_names = [] 

3161 

3162 @staticmethod 

3163 def simplified_name(name): 

3164 # lobster-trace: LRM.Sufficiently_Distinct 

3165 assert isinstance(name, str) 

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

3167 

3168 def all_names(self): 

3169 # lobster-exclude: API for users 

3170 """ All names in the symbol table 

3171 

3172 :rtype: set[str] 

3173 """ 

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

3175 if self.parent: 

3176 rv |= self.parent.all_names() 

3177 return rv 

3178 

3179 def iter_record_objects_by_section(self): 

3180 """API for users 

3181 

3182 Retriving information about the section hierarchy for record objects 

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

3184 sub sections and record objects 

3185 Output: Information about sections and level of sections, 

3186 record objects and levels of record object 

3187 """ 

3188 for record_object in self.iter_record_objects(): 

3189 location = record_object.location.file_name 

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

3191 self.trlc_files.append(location) 

3192 yield location 

3193 if record_object.section: 

3194 object_level = len(record_object.section) - 1 

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

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

3197 self.section_names.append(section) 

3198 yield section.name, level 

3199 yield record_object, object_level 

3200 else: 

3201 object_level = 0 

3202 yield record_object, object_level 

3203 

3204 def iter_record_objects(self): 

3205 # lobster-exclude: API for users 

3206 """ Iterate over all record objects 

3207 

3208 :rtype: iterable[Record_Object] 

3209 """ 

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

3211 if isinstance(item, Package): 

3212 yield from item.symbols.iter_record_objects() 

3213 

3214 elif isinstance(item, Record_Object): 

3215 yield item 

3216 

3217 def values(self, subtype=None): 

3218 # lobster-exclude: API for users 

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

3220 if self.parent: 

3221 yield from self.parent.values(subtype) 

3222 for name in sorted(self.table): 

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

3224 yield self.table[name] 

3225 

3226 def make_visible(self, stab): 

3227 assert isinstance(stab, Symbol_Table) 

3228 self.imported.append(stab) 

3229 

3230 def register(self, mh, entity): 

3231 # lobster-trace: LRM.Duplicate_Types 

3232 # lobster-trace: LRM.Unique_Enumeration_Literals 

3233 # lobster-trace: LRM.Tuple_Unique_Field_Names 

3234 # lobster-trace: LRM.Sufficiently_Distinct 

3235 # lobster-trace: LRM.Unique_Object_Names 

3236 

3237 assert isinstance(mh, Message_Handler) 

3238 assert isinstance(entity, Entity) 

3239 

3240 simple_name = self.simplified_name(entity.name) 

3241 

3242 if self.contains_raw(simple_name): 

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

3244 simplified=True) 

3245 if pdef.name == entity.name: 

3246 mh.error(entity.location, 

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

3248 mh.cross_file_reference(pdef.location)) 

3249 else: 

3250 mh.error(entity.location, 

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

3252 (entity.name, 

3253 pdef.name, 

3254 mh.cross_file_reference(pdef.location))) 

3255 

3256 else: 

3257 self.table[simple_name] = entity 

3258 

3259 def __contains__(self, name): 

3260 # lobster-trace: LRM.Described_Name_Equality 

3261 return self.contains(name) 

3262 

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

3264 # lobster-trace: LRM.Described_Name_Equality 

3265 # lobster-trace: LRM.Sufficiently_Distinct 

3266 # 

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

3268 # table. 

3269 assert isinstance(simple_name, str) 

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

3271 

3272 if simple_name in self.table: 

3273 # No need to continue searching since registering a 

3274 # clashing name would have been stopped 

3275 return precise_name is None or \ 

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

3277 

3278 elif self.parent: 

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

3280 

3281 for stab in self.imported: 

3282 if stab.contains_raw(simple_name, precise_name): 

3283 return True 

3284 

3285 return False 

3286 

3287 def contains(self, name): 

3288 # lobster-trace: LRM.Described_Name_Equality 

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

3290 

3291 :param name: the name to test 

3292 :type name: str 

3293 

3294 :rtype: bool 

3295 """ 

3296 assert isinstance(name, str) 

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

3298 

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

3300 # lobster-trace: LRM.Described_Name_Equality 

3301 # lobster-trace: LRM.Sufficiently_Distinct 

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

3303 

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

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

3306 

3307 :param mh: The message handler to use 

3308 :type mh: Message_Handler 

3309 

3310 :param name: The name to search for 

3311 :type name: str 

3312 

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

3314 is not an instance of the given class 

3315 :type required_subclass: type 

3316 

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

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

3319 :rtype: Entity 

3320 

3321 """ 

3322 assert isinstance(mh, Message_Handler) 

3323 assert isinstance(name, str) 

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

3325 

3326 simple_name = self.simplified_name(name) 

3327 

3328 ptr = self 

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

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

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

3332 rv = ptr.table[simple_name] 

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

3334 return None 

3335 

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

3337 not isinstance(rv, required_subclass): 

3338 mh.error(rv.location, 

3339 "%s %s is not a %s" % 

3340 (rv.__class__.__name__, 

3341 name, 

3342 required_subclass.__name__)) 

3343 return rv 

3344 else: 

3345 ptr = ptr.parent 

3346 

3347 return None 

3348 

3349 def lookup_direct(self, 

3350 mh, 

3351 name, 

3352 error_location, 

3353 required_subclass=None, 

3354 simplified=False): 

3355 # lobster-trace: LRM.Described_Name_Equality 

3356 # lobster-trace: LRM.Sufficiently_Distinct 

3357 # lobster-trace: LRM.Valid_Base_Names 

3358 # lobster-trace: LRM.Valid_Access_Prefixes 

3359 # lobster-trace: LRM.Valid_Function_Prefixes 

3360 """Retrieve an object from the table 

3361 

3362 For example:: 

3363 

3364 pkg = stab.lookup_direct(mh, 

3365 "potato", 

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

3367 Package) 

3368 

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

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

3371 Package, then the following error is issued:: 

3372 

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

3374 

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

3376 

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

3378 

3379 :param mh: The message handler to use 

3380 :type mh: Message_Handler 

3381 

3382 :param name: The name to search for 

3383 :type name: str 

3384 

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

3386 not found 

3387 :type error_location: Location 

3388 

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

3390 is not an instance of the given class 

3391 :type required_subclass: type 

3392 

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

3394 of the actual name 

3395 :type simplified: bool 

3396 

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

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

3399 :returns: the specified entity 

3400 :rtype: Entity 

3401 

3402 """ 

3403 assert isinstance(mh, Message_Handler) 

3404 assert isinstance(name, str) 

3405 assert isinstance(error_location, Location) 

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

3407 assert isinstance(simplified, bool) 

3408 

3409 simple_name = self.simplified_name(name) 

3410 ptr = self 

3411 options = [] 

3412 

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

3414 while ptr: 

3415 if simple_name in ptr.table: 

3416 rv = ptr.table[simple_name] 

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

3418 mh.error(error_location, 

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

3420 (name, 

3421 rv.name)) 

3422 

3423 if required_subclass is not None and \ 

3424 not isinstance(rv, required_subclass): 

3425 mh.error(error_location, 

3426 "%s %s is not a %s" % 

3427 (rv.__class__.__name__, 

3428 name, 

3429 required_subclass.__name__)) 

3430 return rv 

3431 else: 

3432 options += list(item.name 

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

3434 ptr = ptr.parent 

3435 

3436 matches = get_close_matches( 

3437 word = name, 

3438 possibilities = options, 

3439 n = 1) 

3440 

3441 if matches: 

3442 mh.error(error_location, 

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

3444 (name, 

3445 matches[0])) 

3446 else: 

3447 mh.error(error_location, 

3448 "unknown symbol %s" % name) 

3449 

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

3451 # lobster-trace: LRM.Described_Name_Equality 

3452 assert isinstance(mh, Message_Handler) 

3453 assert isinstance(referencing_token, Token) 

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

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

3456 

3457 return self.lookup_direct( 

3458 mh = mh, 

3459 name = referencing_token.value, 

3460 error_location = referencing_token.location, 

3461 required_subclass = required_subclass) 

3462 

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

3464 # lobster-exclude: Debugging feature 

3465 assert isinstance(indent, int) 

3466 assert indent >= 0 

3467 assert isinstance(message, str) 

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

3469 

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

3471 # lobster-exclude: Debugging feature 

3472 if omit_heading: 

3473 new_indent = indent 

3474 else: 

3475 self.write_indent(indent, "Symbol_Table") 

3476 new_indent = indent + 1 

3477 ptr = self 

3478 while ptr: 

3479 for name in ptr.table: 

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

3481 ptr = ptr.parent 

3482 

3483 @classmethod 

3484 def create_global_table(cls, mh): 

3485 # lobster-trace: LRM.Builtin_Types 

3486 # lobster-trace: LRM.Builtin_Functions 

3487 # lobster-trace: LRM.Builtin_Type_Conversion_Functions 

3488 # lobster-trace: LRM.Signature_Len 

3489 # lobster-trace: LRM.Signature_String_End_Functions 

3490 # lobster-trace: LRM.Signature_Matches 

3491 

3492 stab = Symbol_Table() 

3493 stab.register(mh, Builtin_Integer()) 

3494 stab.register(mh, Builtin_Decimal()) 

3495 stab.register(mh, Builtin_Boolean()) 

3496 stab.register(mh, Builtin_String()) 

3497 stab.register(mh, Builtin_Markup_String()) 

3498 stab.register(mh, 

3499 Builtin_Function("len", 1)) 

3500 stab.register(mh, 

3501 Builtin_Function("startswith", 2)) 

3502 stab.register(mh, 

3503 Builtin_Function("endswith", 2)) 

3504 stab.register(mh, 

3505 Builtin_Function("matches", 2)) 

3506 stab.register(mh, 

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

3508 

3509 return stab 

3510 

3511 

3512class Scope: 

3513 def __init__(self): 

3514 # lobster-exclude: Constructor only declares variables 

3515 self.scope = [] 

3516 

3517 def push(self, stab): 

3518 assert isinstance(stab, Symbol_Table) 

3519 self.scope.append(stab) 

3520 

3521 def pop(self): 

3522 self.scope.pop() 

3523 

3524 def contains(self, name): 

3525 assert isinstance(name, str) 

3526 

3527 for stab in reversed(self.scope): 

3528 if stab.contains(name): 

3529 return True 

3530 return False 

3531 

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

3533 assert len(self.scope) >= 1 

3534 assert isinstance(mh, Message_Handler) 

3535 assert isinstance(referencing_token, Token) 

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

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

3538 

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

3540 if stab.contains(referencing_token.value): 

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

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

3543 

3544 def size(self): 

3545 return len(self.scope)