Coverage for trlc/ast.py: 90%

1199 statements  

« prev     ^ index     » next       coverage.py v7.7.1, created at 2025-03-27 00:52 +0000

1#!/usr/bin/env python3 

2# 

3# TRLC - Treat Requirements Like Code 

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

5# Copyright (C) 2024 Florian Schanda 

6# 

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

8# 

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

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

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

12# (at your option) any later version. 

13# 

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

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

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

17# License for more details. 

18# 

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

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

21 

22from abc import ABCMeta, abstractmethod 

23import re 

24 

25from copy import copy 

26from difflib import get_close_matches 

27from enum import Enum, auto 

28from collections import OrderedDict 

29from fractions import Fraction 

30 

31from trlc.errors import TRLC_Error, Location, Message_Handler 

32from trlc.lexer import Token 

33from trlc import math 

34 

35# 

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

37# reference implementation. There are four sections: 

38# 

39# - Valuations deal with concrete values for record objects 

40# - AST expressions deal with the syntax tree 

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

42# - Symbol_Table and scope deals with name resolution 

43# 

44 

45 

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

47# Valuations 

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

49 

50class Value: 

51 # lobster-trace: LRM.Boolean_Values 

52 # lobster-trace: LRM.Integer_Values 

53 # lobster-trace: LRM.Decimal_Values 

54 # lobster-trace: LRM.String_Values 

55 # lobster-trace: LRM.Markup_String_Values 

56 """Polymorphic value for evaluating expressions. 

57 

58 Any record references will be fully resolved. 

59 

60 :attribute location: source location this value comes from 

61 :type: Location 

62 

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

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

65 Record_Reference, Enumeration_Literal_Spec 

66 

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

68 :type: Type 

69 """ 

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

71 assert isinstance(location, Location) 

72 assert value is None or \ 

73 isinstance(value, (str, 

74 int, 

75 bool, 

76 list, # for arrays 

77 dict, # for tuples 

78 Fraction, 

79 Record_Reference, 

80 Enumeration_Literal_Spec)) 

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

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

83 

84 self.location = location 

85 self.value = value 

86 self.typ = typ 

87 

88 def __eq__(self, other): 

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

90 

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

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

93 

94 def resolve_references(self, mh): 

95 assert isinstance(mh, Message_Handler) 

96 

97 if isinstance(self.value, Record_Reference): 

98 self.value.resolve(mh) 

99 

100 

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

102# AST Nodes 

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

104 

105class Node(metaclass=ABCMeta): 

106 """Base class for all AST items. 

107 

108 :attribute location: source location 

109 :type: Location 

110 """ 

111 def __init__(self, location): 

112 # lobster-exclude: Constructor only declares variables 

113 assert isinstance(location, Location) 

114 self.location = location 

115 

116 def set_ast_link(self, tok): 

117 assert isinstance(tok, Token) 

118 tok.ast_link = self 

119 

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

121 # lobster-exclude: Debugging feature 

122 assert isinstance(indent, int) 

123 assert indent >= 0 

124 assert isinstance(message, str) 

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

126 

127 @abstractmethod 

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

129 """Visualise the parse tree. 

130 

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

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

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

134 output like this:: 

135 

136 Symbol_Table 

137 Builtin_Boolean 

138 Builtin_Integer 

139 Builtin_Decimal 

140 Builtin_String 

141 Builtin_Markup_String 

142 Package bar 

143 Symbol_Table 

144 Record_Type MyType 

145 Composite_Component name 

146 Optional: False 

147 Type: String 

148 Checks 

149 Error 'description is too short' 

150 Anchor: description 

151 Binary Binary_Operator.COMP_GT Expression 

152 Type: Boolean 

153 Unary Unary_Operator.STRING_LENGTH Expression 

154 Type: Integer 

155 Name Reference to description 

156 Integer Literal 10 

157 Package instances 

158 Symbol_Table 

159 Record_Object SomeThing 

160 Type: MyType 

161 Field description: "Potato" 

162 Builtin_Function endswith 

163 Builtin_Function len 

164 Builtin_Function matches 

165 Builtin_Function startswith 

166 Builtin_Function oneof 

167 

168 """ 

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

170 assert False, "dump not implemented for %s" % 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, "Type: %s" % 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, "Compilation_Unit (%s)" % 

231 self.location.file_name) 

232 for t_import in self.raw_imports: 

233 self.write_indent(indent + 1, "Import: %s" % t_import.value) 

234 for n_item in self.items: 

235 n_item.dump(indent + 1) 

236 

237 def set_package(self, pkg): 

238 # lobster-trace: LRM.Current_Package 

239 assert isinstance(pkg, Package) 

240 self.package = pkg 

241 

242 def add_import(self, mh, t_import): 

243 # lobster-trace: LRM.Import_Visibility 

244 # lobster-trace: LRM.Self_Imports 

245 assert isinstance(mh, Message_Handler) 

246 assert isinstance(t_import, Token) 

247 assert t_import.kind == "IDENTIFIER" 

248 

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

250 mh.error(t_import.location, 

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

252 

253 # Skip duplicates 

254 for t_previous in self.raw_imports: 

255 if t_previous.value == t_import.value: 

256 mh.warning(t_import.location, 

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

258 return 

259 

260 self.raw_imports.append(t_import) 

261 

262 def resolve_imports(self, mh, stab): 

263 # lobster-trace: LRM.Import_Visibility 

264 assert isinstance(mh, Message_Handler) 

265 assert isinstance(stab, Symbol_Table) 

266 self.imports = set() 

267 for t_import in self.raw_imports: 

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

269 # generate more error later. 

270 try: 

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

272 self.imports.add(a_import) 

273 a_import.set_ast_link(t_import) 

274 except TRLC_Error: 

275 pass 

276 

277 def is_visible(self, n_pkg): 

278 # lobster-trace: LRM.Import_Visibility 

279 assert self.imports is not None 

280 assert isinstance(n_pkg, Package) 

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

282 

283 def add_item(self, node): 

284 # lobster-trace: LRM.RSL_File 

285 # lobster-trace: LRM.TRLC_File 

286 assert isinstance(node, (Concrete_Type, 

287 Check_Block, 

288 Record_Object)), \ 

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

290 self.items.append(node) 

291 

292 

293class Check(Node): 

294 """User defined check 

295 

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

297 

298 checks T { 

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

300 ^^^^^^^^^^^^^^^^^^^^^^^1 ^2 ^3 ^4 

301 

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

303 :type: Composite_Type 

304 

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

306 :type: Expression 

307 

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

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

310 :type: Composite_Component 

311 

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

313 not specified the default is 'error') 

314 :type: str 

315 

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

317 :type: str 

318 """ 

319 def __init__(self, 

320 n_type, 

321 n_expr, 

322 n_anchor, 

323 severity, 

324 t_message, 

325 extrainfo): 

326 # lobster-trace: LRM.Check_Block 

327 assert isinstance(n_type, Composite_Type) 

328 assert isinstance(n_expr, Expression) 

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

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

331 assert isinstance(t_message, Token) 

332 assert t_message.kind == "STRING" 

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

334 super().__init__(t_message.location) 

335 

336 self.n_type = n_type 

337 self.n_expr = n_expr 

338 self.n_anchor = n_anchor 

339 self.severity = severity 

340 # lobster-trace: LRM.No_Newlines_In_Message 

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

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

343 # raised is non-fatal. 

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

345 self.extrainfo = extrainfo 

346 

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

348 # lobster-exclude: Debugging feature 

349 if self.severity == "warning": 

350 self.write_indent(indent, "Warning '%s'" % self.message) 

351 elif self.severity == "error": 

352 self.write_indent(indent, "Error '%s'" % self.message) 

353 else: 

354 self.write_indent(indent, "Fatal error '%s'" % self.message) 

355 if self.n_anchor: 

356 self.write_indent(indent + 1, "Anchor: %s" % self.n_anchor.name) 

357 self.n_expr.dump(indent + 1) 

358 

359 def get_real_location(self, composite_object): 

360 # lobster-exclude: LRM.Anchoring 

361 assert isinstance(composite_object, (Record_Object, 

362 Tuple_Aggregate)) 

363 if isinstance(composite_object, Record_Object): 

364 fields = composite_object.field 

365 else: 

366 fields = composite_object.value 

367 

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

369 return composite_object.location 

370 else: 

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

372 

373 def perform(self, mh, composite_object): 

374 # lobster-trace: LRM.Check_Messages 

375 # lobster-trace: LRM.Check_Severity 

376 assert isinstance(mh, Message_Handler) 

377 assert isinstance(composite_object, (Record_Object, 

378 Tuple_Aggregate)) 

379 

380 if isinstance(composite_object, Record_Object): 

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

382 else: 

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

384 assert isinstance(result.value, bool) 

385 

386 if not result.value: 

387 loc = self.get_real_location(composite_object) 

388 if self.severity == "warning": 

389 mh.warning(location = loc, 

390 message = self.message, 

391 explanation = self.extrainfo, 

392 user = True) 

393 else: 

394 mh.error(location = loc, 

395 message = self.message, 

396 explanation = self.extrainfo, 

397 fatal = self.severity == "fatal", 

398 user = True) 

399 return False 

400 

401 return True 

402 

403############################################################################## 

404# AST Nodes (Expressions) 

405############################################################################## 

406 

407 

408class Unary_Operator(Enum): 

409 # lobster-exclude: Utility enumeration for unary operators 

410 MINUS = auto() 

411 PLUS = auto() 

412 LOGICAL_NOT = auto() 

413 ABSOLUTE_VALUE = auto() 

414 

415 STRING_LENGTH = auto() 

416 ARRAY_LENGTH = auto() 

417 

418 CONVERSION_TO_INT = auto() 

419 CONVERSION_TO_DECIMAL = auto() 

420 

421 

422class Binary_Operator(Enum): 

423 # lobster-exclude: Utility enumeration for binary operators 

424 LOGICAL_AND = auto() # Short-circuit 

425 LOGICAL_OR = auto() # Short-circuit 

426 LOGICAL_XOR = auto() 

427 LOGICAL_IMPLIES = auto() # Short-circuit 

428 

429 COMP_EQ = auto() 

430 COMP_NEQ = auto() 

431 COMP_LT = auto() 

432 COMP_LEQ = auto() 

433 COMP_GT = auto() 

434 COMP_GEQ = auto() 

435 

436 STRING_CONTAINS = auto() 

437 STRING_STARTSWITH = auto() 

438 STRING_ENDSWITH = auto() 

439 STRING_REGEX = auto() 

440 

441 ARRAY_CONTAINS = auto() 

442 

443 PLUS = auto() 

444 MINUS = auto() 

445 TIMES = auto() 

446 DIVIDE = auto() 

447 REMAINDER = auto() 

448 

449 POWER = auto() 

450 

451 INDEX = auto() 

452 

453 

454class Expression(Node, metaclass=ABCMeta): 

455 """Abstract base class for all expressions. 

456 

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

458 :type: Type 

459 """ 

460 def __init__(self, location, typ): 

461 # lobster-exclude: Constructor only declares variables 

462 super().__init__(location) 

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

464 self.typ = typ 

465 

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

467 """Evaluate the expression in the given context 

468 

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

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

471 dictionary that maps names (such as record fields or 

472 quantified variables) to expressions. 

473 

474 :param mh: the message handler to use 

475 :type mh: Message_Handler 

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

477 :type context: dict[str, Expression] 

478 :raise TRLC_Error: if the expression cannot be evaluated 

479 :return: result of the evaluation 

480 :rtype: Value 

481 """ 

482 assert isinstance(mh, Message_Handler) 

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

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

485 self.__class__.__name__ 

486 

487 @abstractmethod 

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

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

490 self.__class__.__name__ 

491 

492 def ensure_type(self, mh, typ): 

493 # lobster-trace: LRM.Restricted_Null 

494 # lobster-trace: LRM.Null_Is_Invalid 

495 

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

497 if self.typ is None: 

498 mh.error(self.location, 

499 "null is not permitted here") 

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

501 mh.error(self.location, 

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

503 (typ.__name__, 

504 self.typ.__class__.__name__)) 

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

506 mh.error(self.location, 

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

508 (typ.name, 

509 self.typ.name)) 

510 

511 def resolve_references(self, mh): 

512 assert isinstance(mh, Message_Handler) 

513 

514 @abstractmethod 

515 def can_be_null(self): 

516 """Test if the expression could return null 

517 

518 Checks the expression if it could generate a null value 

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

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

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

522 occur earlier. 

523 

524 :return: possibility of encountering null 

525 :rtype: bool 

526 

527 """ 

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

529 self.__class__.__name__ 

530 

531 

532class Implicit_Null(Expression): 

533 """Synthesised null values 

534 

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

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

537 implicit null expression for this. 

538 

539 For example given this TRLC type:: 

540 

541 type T { 

542 x optional Integer 

543 } 

544 

545 And this declaration:: 

546 

547 T Potato {} 

548 

549 Then the field mapping for Potato will be:: 

550 

551 {x: Implicit_Null} 

552 

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

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

555 that can appear in check expressions. 

556 

557 """ 

558 def __init__(self, composite_object, composite_component): 

559 # lobster-trace: LRM.Unspecified_Optional_Components 

560 assert isinstance(composite_object, (Record_Object, 

561 Tuple_Aggregate)) 

562 assert isinstance(composite_component, Composite_Component) 

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

564 

565 def to_string(self): 

566 return "null" 

567 

568 def evaluate(self, mh, context): 

569 # lobster-trace: LRM.Unspecified_Optional_Components 

570 assert isinstance(mh, Message_Handler) 

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

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

573 

574 def to_python_object(self): 

575 return None 

576 

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

578 # lobster-exclude: Debugging feature 

579 self.write_indent(indent, "Implicit_Null") 

580 

581 def can_be_null(self): 

582 return True 

583 

584 

585class Literal(Expression, metaclass=ABCMeta): 

586 """Abstract base for all Literals 

587 

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

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

590 check if you are dealing with a literal:: 

591 

592 isinstance(my_expression, Literal) 

593 

594 """ 

595 @abstractmethod 

596 def to_python_object(self): 

597 assert False 

598 

599 

600class Null_Literal(Literal): 

601 # lobster-trace: LRM.Primary 

602 """The null literal 

603 

604 This can appear in check expressions:: 

605 

606 a /= null implies a > 5 

607 ^^^^ 

608 

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

610 values that appear in record objects. 

611 

612 """ 

613 def __init__(self, token): 

614 assert isinstance(token, Token) 

615 assert token.kind == "KEYWORD" 

616 assert token.value == "null" 

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

618 

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

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

621 

622 def to_string(self): 

623 return "null" 

624 

625 def evaluate(self, mh, context): 

626 assert isinstance(mh, Message_Handler) 

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

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

629 

630 def to_python_object(self): 

631 return None 

632 

633 def can_be_null(self): 

634 return True 

635 

636 

637class Integer_Literal(Literal): 

638 # lobster-trace: LRM.Integer_Values 

639 # lobster-trace: LRM.Primary 

640 """Integer literals 

641 

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

643 actually a unary negation expression, operating on a positive 

644 integer literal:: 

645 

646 x == -5 

647 

648 This would create the following tree:: 

649 

650 OP_EQUALITY 

651 NAME_REFERENCE x 

652 UNARY_EXPRESSION - 

653 INTEGER_LITERAL 5 

654 

655 :attribute value: the non-negative integer value 

656 :type: int 

657 """ 

658 def __init__(self, token, typ): 

659 assert isinstance(token, Token) 

660 assert token.kind == "INTEGER" 

661 assert isinstance(typ, Builtin_Integer) 

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

663 

664 self.value = token.value 

665 

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

667 self.write_indent(indent, "Integer Literal %u" % self.value) 

668 

669 def to_string(self): 

670 return str(self.value) 

671 

672 def evaluate(self, mh, context): 

673 assert isinstance(mh, Message_Handler) 

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

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

676 

677 def to_python_object(self): 

678 return self.value 

679 

680 def can_be_null(self): 

681 return False 

682 

683 

684class Decimal_Literal(Literal): 

685 # lobster-trace: LRM.Decimal_Values 

686 # lobster-trace: LRM.Primary 

687 """Decimal literals 

688 

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

690 actually a unary negation expression, operating on a positive 

691 decimal literal:: 

692 

693 x == -5.0 

694 

695 This would create the following tree:: 

696 

697 OP_EQUALITY 

698 NAME_REFERENCE x 

699 UNARY_EXPRESSION - 

700 DECIMAL_LITERAL 5.0 

701 

702 :attribute value: the non-negative decimal value 

703 :type: fractions.Fraction 

704 """ 

705 def __init__(self, token, typ): 

706 assert isinstance(token, Token) 

707 assert token.kind == "DECIMAL" 

708 assert isinstance(typ, Builtin_Decimal) 

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

710 

711 self.value = token.value 

712 

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

714 self.write_indent(indent, "Decimal Literal %u" % self.value) 

715 

716 def to_string(self): 

717 return str(self.value) 

718 

719 def evaluate(self, mh, context): 

720 assert isinstance(mh, Message_Handler) 

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

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

723 

724 def to_python_object(self): 

725 return float(self.value) 

726 

727 def can_be_null(self): 

728 return False 

729 

730 

731class String_Literal(Literal): 

732 # lobster-trace: LRM.String_Values 

733 # lobster-trace: LRM.Markup_String_Values 

734 # lobster-trace: LRM.Primary 

735 """String literals 

736 

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

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

739 

740 "foo\\"bar" 

741 

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

743 

744 :attribute value: string content 

745 :type: str 

746 

747 :attribute references: resolved references of a markup string 

748 :type: list[Record_Reference] 

749 

750 """ 

751 def __init__(self, token, typ): 

752 assert isinstance(token, Token) 

753 assert token.kind == "STRING" 

754 assert isinstance(typ, Builtin_String) 

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

756 

757 self.value = token.value 

758 self.has_references = isinstance(typ, Builtin_Markup_String) 

759 self.references = [] 

760 

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

762 self.write_indent(indent, "String Literal %s" % repr(self.value)) 

763 if self.has_references: 

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

765 for ref in self.references: 

766 ref.dump(indent + 2) 

767 

768 def to_string(self): 

769 return self.value 

770 

771 def evaluate(self, mh, context): 

772 assert isinstance(mh, Message_Handler) 

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

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

775 

776 def to_python_object(self): 

777 return self.value 

778 

779 def resolve_references(self, mh): 

780 assert isinstance(mh, Message_Handler) 

781 for ref in self.references: 

782 ref.resolve_references(mh) 

783 

784 def can_be_null(self): 

785 return False 

786 

787 

788class Boolean_Literal(Literal): 

789 # lobster-trace: LRM.Boolean_Values 

790 # lobster-trace: LRM.Primary 

791 """Boolean values 

792 

793 :attribute value: the boolean value 

794 :type: bool 

795 """ 

796 def __init__(self, token, typ): 

797 assert isinstance(token, Token) 

798 assert token.kind == "KEYWORD" 

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

800 assert isinstance(typ, Builtin_Boolean) 

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

802 

803 self.value = token.value == "true" 

804 

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

806 self.write_indent(indent, "Boolean Literal %s" % self.value) 

807 

808 def to_string(self): 

809 return str(self.value) 

810 

811 def evaluate(self, mh, context): 

812 assert isinstance(mh, Message_Handler) 

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

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

815 

816 def to_python_object(self): 

817 return self.value 

818 

819 def can_be_null(self): 

820 return False 

821 

822 

823class Enumeration_Literal(Literal): 

824 """Enumeration values 

825 

826 Note that this is distinct from 

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

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

829 

830 foo != my_enum.POTATO 

831 ^^^^^^^^^^^^^^ 

832 

833 To get to the string value of the enumeration literal 

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

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

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

837 ``enum_lit.value.n_typ.name``. 

838 

839 :attribute value: enumeration value 

840 :type: Enumeration_Literal_Spec 

841 

842 """ 

843 def __init__(self, location, literal): 

844 # lobster-exclude: Constructor only declares variables 

845 assert isinstance(literal, Enumeration_Literal_Spec) 

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

847 

848 self.value = literal 

849 

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

851 # lobster-exclude: Debugging feature 

852 self.write_indent(indent, 

853 "Enumeration Literal %s.%s" % (self.typ.name, 

854 self.value.name)) 

855 

856 def to_string(self): 

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

858 

859 def evaluate(self, mh, context): 

860 assert isinstance(mh, Message_Handler) 

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

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

863 

864 def to_python_object(self): 

865 return self.value.name 

866 

867 def can_be_null(self): 

868 return False 

869 

870 

871class Array_Aggregate(Expression): 

872 """Instances of array types 

873 

874 This is created when assigning to array components:: 

875 

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

877 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 

878 

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

880 limited: 

881 

882 * :class:`Literal` 

883 * :class:`Array_Aggregate` 

884 * :class:`Record_Reference` 

885 

886 :attribute value: contents of the array 

887 :type: list[Expression] 

888 

889 """ 

890 def __init__(self, location, typ): 

891 # lobster-trace: LRM.Record_Object_Declaration 

892 

893 super().__init__(location, typ) 

894 self.value = [] 

895 

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

897 # lobster-exclude: Debugging feature 

898 self.write_indent(indent, "Array_Aggregate") 

899 for n_value in self.value: 

900 n_value.dump(indent + 1) 

901 

902 def append(self, value): 

903 assert isinstance(value, (Literal, 

904 Unary_Expression, 

905 Array_Aggregate, 

906 Tuple_Aggregate, 

907 Record_Reference)) 

908 self.value.append(value) 

909 

910 def to_string(self): 

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

912 

913 def evaluate(self, mh, context): 

914 assert isinstance(mh, Message_Handler) 

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

916 return Value(self.location, 

917 list(element.evaluate(mh, context) 

918 for element in self.value), 

919 self.typ) 

920 

921 def resolve_references(self, mh): 

922 assert isinstance(mh, Message_Handler) 

923 

924 for val in self.value: 

925 val.resolve_references(mh) 

926 

927 def to_python_object(self): 

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

929 

930 def can_be_null(self): 

931 return False 

932 

933 

934class Tuple_Aggregate(Expression): 

935 """Instances of a tuple 

936 

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

938 two forms, the ordinary form:: 

939 

940 coordinate = (12.3, 40.0) 

941 ^^^^^^^^^^^^ 

942 

943 And the separator form:: 

944 

945 item = 12345@42 

946 ^^^^^^^^ 

947 

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

949 syntactic sugar. 

950 

951 :attribute value: contents of the tuple 

952 :type: dict[str, Expression] 

953 

954 """ 

955 def __init__(self, location, typ): 

956 # lobster-trace: LRM.Unspecified_Optional_Components 

957 # lobster-trace: LRM.Record_Object_Declaration 

958 

959 super().__init__(location, typ) 

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

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

962 

963 def assign(self, field, value): 

964 assert isinstance(field, str) 

965 assert isinstance(value, (Literal, 

966 Unary_Expression, 

967 Tuple_Aggregate, 

968 Record_Reference)), \ 

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

970 assert field in self.typ.components 

971 

972 self.value[field] = value 

973 

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

975 # lobster-exclude: Debugging feature 

976 self.write_indent(indent, "Tuple_Aggregate") 

977 self.write_indent(indent + 1, "Type: %s" % self.typ.name) 

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

979 if isinstance(n_item, Composite_Component): 

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

981 

982 def to_string(self): 

983 first = True 

984 if self.typ.has_separators(): 

985 rv = "" 

986 else: 

987 rv = "(" 

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

989 if isinstance(n_item, Separator): 

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

991 elif first: 

992 first = False 

993 else: 

994 rv += ", " 

995 

996 if isinstance(n_item, Composite_Component): 

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

998 if self.typ.has_separators(): 

999 rv = "" 

1000 else: 

1001 rv = ")" 

1002 return rv 

1003 

1004 def evaluate(self, mh, context): 

1005 assert isinstance(mh, Message_Handler) 

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

1007 return Value(self.location, 

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

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

1010 self.typ) 

1011 

1012 def resolve_references(self, mh): 

1013 assert isinstance(mh, Message_Handler) 

1014 

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

1016 val.resolve_references(mh) 

1017 

1018 def to_python_object(self): 

1019 return {name: value.to_python_object() 

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

1021 

1022 def can_be_null(self): 

1023 return False 

1024 

1025 

1026class Record_Reference(Expression): 

1027 """Reference to another record object 

1028 

1029 This can appear in record object declarations:: 

1030 

1031 Requirement Kitten { 

1032 depends_on = Other_Package.Cat 

1033 ^1 ^2 

1034 } 

1035 

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

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

1038 the target attribute. 

1039 

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

1041 immediately resolved on parsing in the TRLC language. 

1042 

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

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

1045 in this AST node. 

1046 

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

1048 :type: str 

1049 

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

1051 :type: Record_Object 

1052 

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

1054 :type: Package 

1055 

1056 """ 

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

1058 # lobster-exclude: Constructor only declares variables 

1059 assert isinstance(location, Location) 

1060 assert isinstance(name, str) 

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

1062 assert isinstance(package, Package) 

1063 super().__init__(location, typ) 

1064 

1065 self.name = name 

1066 self.target = None 

1067 self.package = package 

1068 

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

1070 # lobster-exclude: Debugging feature 

1071 self.write_indent(indent, "Record Reference %s" % self.name) 

1072 self.write_indent(indent + 1, 

1073 "Resolved: %s" % (self.target is not None)) 

1074 

1075 def to_string(self): 

1076 return self.name 

1077 

1078 def evaluate(self, mh, context): 

1079 assert isinstance(mh, Message_Handler) 

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

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

1082 

1083 def resolve_references(self, mh): 

1084 # lobster-trace: LRM.References_To_Extensions 

1085 assert isinstance(mh, Message_Handler) 

1086 

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

1088 mh = mh, 

1089 name = self.name, 

1090 error_location = self.location, 

1091 required_subclass = Record_Object) 

1092 if self.typ is None: 

1093 self.typ = self.target.n_typ 

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

1095 mh.error(self.location, 

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

1097 (self.typ.name, 

1098 self.target.name, 

1099 self.target.n_typ.name)) 

1100 

1101 def to_python_object(self): 

1102 return self.target.fully_qualified_name() 

1103 

1104 def can_be_null(self): 

1105 return False 

1106 

1107 

1108class Name_Reference(Expression): 

1109 # lobster-trace: LRM.Qualified_Name 

1110 # lobster-trace: LRM.Static_Regular_Expression 

1111 

1112 """Reference to a name 

1113 

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

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

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

1117 

1118 For example:: 

1119 

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

1121 ^1 ^2 

1122 

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

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

1125 :class:`Quantified_Variable`. 

1126 

1127 :attribute entity: the entity named here 

1128 :type: Composite_Component, Quantified_Variable 

1129 """ 

1130 def __init__(self, location, entity): 

1131 assert isinstance(entity, (Composite_Component, 

1132 Quantified_Variable)) 

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

1134 self.entity = entity 

1135 

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

1137 self.write_indent(indent, "Name Reference to %s" % self.entity.name) 

1138 

1139 def to_string(self): 

1140 return self.entity.name 

1141 

1142 def evaluate(self, mh, context): 

1143 assert isinstance(mh, Message_Handler) 

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

1145 

1146 if context is None: 

1147 mh.error(self.location, 

1148 "cannot be used in a static context") 

1149 

1150 assert self.entity.name in context 

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

1152 

1153 def can_be_null(self): 

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

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

1156 # optional. 

1157 if isinstance(self.entity, Composite_Component): 

1158 return self.entity.optional 

1159 else: 

1160 return False 

1161 

1162 

1163class Unary_Expression(Expression): 

1164 """Expression with only one operand 

1165 

1166 This captures the following operations: 

1167 

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

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

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

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

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

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

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

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

1176 

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

1178 

1179 :attribute operator: the operation 

1180 :type: Unary_Operator 

1181 

1182 :attribute n_operand: the expression we operate on 

1183 :type: Expression 

1184 

1185 """ 

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

1187 # lobster-trace: LRM.Simple_Expression 

1188 # lobster-trace: LRM.Relation 

1189 # lobster-trace: LRM.Factor 

1190 # lobster-trace: LRM.Signature_Len 

1191 # lobster-trace: LRM.Signature_Type_Conversion 

1192 

1193 super().__init__(location, typ) 

1194 assert isinstance(mh, Message_Handler) 

1195 assert isinstance(operator, Unary_Operator) 

1196 assert isinstance(n_operand, Expression) 

1197 self.operator = operator 

1198 self.n_operand = n_operand 

1199 

1200 if operator in (Unary_Operator.MINUS, 

1201 Unary_Operator.PLUS, 

1202 Unary_Operator.ABSOLUTE_VALUE): 

1203 self.n_operand.ensure_type(mh, Builtin_Numeric_Type) 

1204 elif operator == Unary_Operator.LOGICAL_NOT: 

1205 self.n_operand.ensure_type(mh, Builtin_Boolean) 

1206 elif operator == Unary_Operator.STRING_LENGTH: 

1207 self.n_operand.ensure_type(mh, Builtin_String) 

1208 elif operator == Unary_Operator.ARRAY_LENGTH: 

1209 self.n_operand.ensure_type(mh, Array_Type) 

1210 elif operator == Unary_Operator.CONVERSION_TO_INT: 

1211 self.n_operand.ensure_type(mh, Builtin_Numeric_Type) 

1212 elif operator == Unary_Operator.CONVERSION_TO_DECIMAL: 

1213 self.n_operand.ensure_type(mh, Builtin_Numeric_Type) 

1214 else: 

1215 mh.ice_loc(self.location, 

1216 "unexpected unary operation %s" % operator) 

1217 

1218 def to_string(self): 

1219 prefix_operators = { 

1220 Unary_Operator.MINUS : "-", 

1221 Unary_Operator.PLUS : "+", 

1222 Unary_Operator.ABSOLUTE_VALUE : "abs ", 

1223 Unary_Operator.LOGICAL_NOT : "not ", 

1224 } 

1225 function_calls = { 

1226 Unary_Operator.STRING_LENGTH : "len", 

1227 Unary_Operator.ARRAY_LENGTH : "len", 

1228 Unary_Operator.CONVERSION_TO_INT : "Integer", 

1229 Unary_Operator.CONVERSION_TO_DECIMAL : "Decimal" 

1230 } 

1231 

1232 if self.operator in prefix_operators: 

1233 return prefix_operators[self.operator] + \ 

1234 self.n_operand.to_string() 

1235 

1236 elif self.operator in function_calls: 

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

1238 self.n_operand.to_string()) 

1239 

1240 else: 

1241 assert False 

1242 

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

1244 # lobster-exclude: Debugging feature 

1245 self.write_indent(indent, "Unary %s Expression" % self.operator) 

1246 self.write_indent(indent + 1, "Type: %s" % self.typ.name) 

1247 self.n_operand.dump(indent + 1) 

1248 

1249 def evaluate(self, mh, context): 

1250 # lobster-trace: LRM.Null_Is_Invalid 

1251 # lobster-trace: LRM.Signature_Len 

1252 # lobster-trace: LRM.Signature_Type_Conversion 

1253 # lobster-trace: LRM.Len_Semantics 

1254 # lobster-trace: LRM.Integer_Conversion_Semantics 

1255 # lobster-trace: LRM.Decimal_Conversion_Semantics 

1256 

1257 assert isinstance(mh, Message_Handler) 

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

1259 

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

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

1262 mh.error(v_operand.location, 

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

1264 (self.to_string(), 

1265 mh.cross_file_reference(self.location))) 

1266 

1267 if self.operator == Unary_Operator.MINUS: 

1268 return Value(location = self.location, 

1269 value = -v_operand.value, 

1270 typ = self.typ) 

1271 elif self.operator == Unary_Operator.PLUS: 

1272 return Value(location = self.location, 

1273 value = +v_operand.value, 

1274 typ = self.typ) 

1275 elif self.operator == Unary_Operator.LOGICAL_NOT: 

1276 return Value(location = self.location, 

1277 value = not v_operand.value, 

1278 typ = self.typ) 

1279 elif self.operator == Unary_Operator.ABSOLUTE_VALUE: 

1280 return Value(location = self.location, 

1281 value = abs(v_operand.value), 

1282 typ = self.typ) 

1283 elif self.operator in (Unary_Operator.STRING_LENGTH, 

1284 Unary_Operator.ARRAY_LENGTH): 

1285 return Value(location = self.location, 

1286 value = len(v_operand.value), 

1287 typ = self.typ) 

1288 elif self.operator == Unary_Operator.CONVERSION_TO_INT: 

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

1290 return Value( 

1291 location = self.location, 

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

1293 typ = self.typ) 

1294 else: 

1295 return Value(location = self.location, 

1296 value = v_operand.value, 

1297 typ = self.typ) 

1298 elif self.operator == Unary_Operator.CONVERSION_TO_DECIMAL: 

1299 return Value(location = self.location, 

1300 value = Fraction(v_operand.value), 

1301 typ = self.typ) 

1302 else: 

1303 mh.ice_loc(self.location, 

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

1305 

1306 def to_python_object(self): 

1307 assert self.operator in (Unary_Operator.MINUS, 

1308 Unary_Operator.PLUS) 

1309 val = self.n_operand.to_python_object() 

1310 if self.operator == Unary_Operator.MINUS: 

1311 return -val 

1312 else: 

1313 return val 

1314 

1315 def can_be_null(self): 

1316 return False 

1317 

1318 

1319class Binary_Expression(Expression): 

1320 """Expression with two operands 

1321 

1322 This captures the following operations: 

1323 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1346 

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

1348 

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

1350 rationals and strings. 

1351 

1352 :attribute operator: the operation 

1353 :type: Binary_Operator 

1354 

1355 :attribute n_lhs: the first operand 

1356 :type: Expression 

1357 

1358 :attribute n_lhs: the second operand 

1359 :type: Expression 

1360 

1361 """ 

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

1363 # lobster-trace: LRM.Expression 

1364 # lobster-trace: LRM.Relation 

1365 # lobster-trace: LRM.Simple_Expression 

1366 # lobster-trace: LRM.Term 

1367 # lobster-trace: LRM.Factor 

1368 # lobster-trace: LRM.Signature_String_End_Functions 

1369 # lobster-trace: LRM.Signature_Matches 

1370 

1371 super().__init__(location, typ) 

1372 assert isinstance(mh, Message_Handler) 

1373 assert isinstance(operator, Binary_Operator) 

1374 assert isinstance(n_lhs, Expression) 

1375 assert isinstance(n_rhs, Expression) 

1376 self.operator = operator 

1377 self.n_lhs = n_lhs 

1378 self.n_rhs = n_rhs 

1379 

1380 if operator in (Binary_Operator.LOGICAL_AND, 

1381 Binary_Operator.LOGICAL_OR, 

1382 Binary_Operator.LOGICAL_XOR, 

1383 Binary_Operator.LOGICAL_IMPLIES): 

1384 self.n_lhs.ensure_type(mh, Builtin_Boolean) 

1385 self.n_rhs.ensure_type(mh, Builtin_Boolean) 

1386 

1387 elif operator in (Binary_Operator.COMP_EQ, 

1388 Binary_Operator.COMP_NEQ): 

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

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

1391 pass 

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

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

1394 # types match 

1395 mh.error(self.location, 

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

1397 (self.n_lhs.typ.name, 

1398 self.n_rhs.typ.name)) 

1399 

1400 elif operator in (Binary_Operator.COMP_LT, 

1401 Binary_Operator.COMP_LEQ, 

1402 Binary_Operator.COMP_GT, 

1403 Binary_Operator.COMP_GEQ): 

1404 self.n_lhs.ensure_type(mh, Builtin_Numeric_Type) 

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

1406 

1407 elif operator in (Binary_Operator.STRING_CONTAINS, 

1408 Binary_Operator.STRING_STARTSWITH, 

1409 Binary_Operator.STRING_ENDSWITH, 

1410 Binary_Operator.STRING_REGEX): 

1411 self.n_lhs.ensure_type(mh, Builtin_String) 

1412 self.n_rhs.ensure_type(mh, Builtin_String) 

1413 

1414 elif operator == Binary_Operator.ARRAY_CONTAINS: 

1415 self.n_rhs.ensure_type(mh, Array_Type) 

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

1417 

1418 elif operator == Binary_Operator.PLUS: 

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

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

1421 else: 

1422 self.n_lhs.ensure_type(mh, Builtin_String) 

1423 self.n_rhs.ensure_type(mh, Builtin_String) 

1424 

1425 elif operator in (Binary_Operator.MINUS, 

1426 Binary_Operator.TIMES, 

1427 Binary_Operator.DIVIDE): 

1428 self.n_lhs.ensure_type(mh, Builtin_Numeric_Type) 

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

1430 

1431 elif operator == Binary_Operator.POWER: 

1432 self.n_lhs.ensure_type(mh, Builtin_Numeric_Type) 

1433 self.n_rhs.ensure_type(mh, Builtin_Integer) 

1434 

1435 elif operator == Binary_Operator.REMAINDER: 

1436 self.n_lhs.ensure_type(mh, Builtin_Integer) 

1437 self.n_rhs.ensure_type(mh, Builtin_Integer) 

1438 

1439 elif operator == Binary_Operator.INDEX: 

1440 self.n_lhs.ensure_type(mh, Array_Type) 

1441 self.n_rhs.ensure_type(mh, Builtin_Integer) 

1442 

1443 else: 

1444 mh.ice_loc(self.location, 

1445 "unexpected binary operation %s" % operator) 

1446 

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

1448 # lobster-exclude: Debugging feature 

1449 self.write_indent(indent, "Binary %s Expression" % self.operator) 

1450 self.write_indent(indent + 1, "Type: %s" % self.typ.name) 

1451 self.n_lhs.dump(indent + 1) 

1452 self.n_rhs.dump(indent + 1) 

1453 

1454 def to_string(self): 

1455 infix_operators = { 

1456 Binary_Operator.LOGICAL_AND : "and", 

1457 Binary_Operator.LOGICAL_OR : "or", 

1458 Binary_Operator.LOGICAL_XOR : "xor", 

1459 Binary_Operator.LOGICAL_IMPLIES : "implies", 

1460 Binary_Operator.COMP_EQ : "==", 

1461 Binary_Operator.COMP_NEQ : "!=", 

1462 Binary_Operator.COMP_LT : "<", 

1463 Binary_Operator.COMP_LEQ : "<=", 

1464 Binary_Operator.COMP_GT : ">", 

1465 Binary_Operator.COMP_GEQ : ">=", 

1466 Binary_Operator.STRING_CONTAINS : "in", 

1467 Binary_Operator.ARRAY_CONTAINS : "in", 

1468 Binary_Operator.PLUS : "+", 

1469 Binary_Operator.MINUS : "-", 

1470 Binary_Operator.TIMES : "*", 

1471 Binary_Operator.DIVIDE : "/", 

1472 Binary_Operator.REMAINDER : "%", 

1473 Binary_Operator.POWER : "**", 

1474 } 

1475 string_functions = { 

1476 Binary_Operator.STRING_STARTSWITH : "startswith", 

1477 Binary_Operator.STRING_ENDSWITH : "endswith", 

1478 Binary_Operator.STRING_REGEX : "matches", 

1479 } 

1480 

1481 if self.operator in infix_operators: 

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

1483 infix_operators[self.operator], 

1484 self.n_rhs.to_string()) 

1485 

1486 elif self.operator in string_functions: 

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

1488 self.n_lhs.to_string(), 

1489 self.n_rhs.to_string()) 

1490 

1491 elif self.operator == Binary_Operator.INDEX: 

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

1493 self.n_rhs.to_string()) 

1494 

1495 else: 

1496 assert False 

1497 

1498 def evaluate(self, mh, context): 

1499 # lobster-trace: LRM.Null_Equivalence 

1500 # lobster-trace: LRM.Null_Is_Invalid 

1501 # lobster-trace: LRM.Signature_String_End_Functions 

1502 # lobster-trace: LRM.Signature_Matches 

1503 # lobster-trace: LRM.Startswith_Semantics 

1504 # lobster-trace: LRM.Endswith_Semantics 

1505 # lobster-trace: LRM.Matches_Semantics 

1506 

1507 assert isinstance(mh, Message_Handler) 

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

1509 

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

1511 if v_lhs.value is None and \ 

1512 self.operator not in (Binary_Operator.COMP_EQ, 

1513 Binary_Operator.COMP_NEQ): 

1514 mh.error(v_lhs.location, 

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

1516 (self.to_string(), 

1517 mh.cross_file_reference(self.location))) 

1518 

1519 # Check for the short-circuit operators first 

1520 if self.operator == Binary_Operator.LOGICAL_AND: 

1521 assert isinstance(v_lhs.value, bool) 

1522 if v_lhs.value: 

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

1524 else: 

1525 return v_lhs 

1526 

1527 elif self.operator == Binary_Operator.LOGICAL_OR: 

1528 assert isinstance(v_lhs.value, bool) 

1529 if v_lhs.value: 

1530 return v_lhs 

1531 else: 

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

1533 

1534 elif self.operator == Binary_Operator.LOGICAL_IMPLIES: 

1535 assert isinstance(v_lhs.value, bool) 

1536 if v_lhs.value: 

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

1538 else: 

1539 return Value(location = self.location, 

1540 value = True, 

1541 typ = self.typ) 

1542 

1543 # Otherwise, evaluate RHS and do the operation 

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

1545 if v_rhs.value is None and \ 

1546 self.operator not in (Binary_Operator.COMP_EQ, 

1547 Binary_Operator.COMP_NEQ): 

1548 mh.error(v_rhs.location, 

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

1550 (self.to_string(), 

1551 mh.cross_file_reference(self.location))) 

1552 

1553 if self.operator == Binary_Operator.LOGICAL_XOR: 

1554 assert isinstance(v_lhs.value, bool) 

1555 assert isinstance(v_rhs.value, bool) 

1556 return Value(location = self.location, 

1557 value = v_lhs.value ^ v_rhs.value, 

1558 typ = self.typ) 

1559 

1560 elif self.operator == Binary_Operator.COMP_EQ: 

1561 return Value(location = self.location, 

1562 value = v_lhs.value == v_rhs.value, 

1563 typ = self.typ) 

1564 

1565 elif self.operator == Binary_Operator.COMP_NEQ: 

1566 return Value(location = self.location, 

1567 value = v_lhs.value != v_rhs.value, 

1568 typ = self.typ) 

1569 

1570 elif self.operator in (Binary_Operator.COMP_LT, 

1571 Binary_Operator.COMP_LEQ, 

1572 Binary_Operator.COMP_GT, 

1573 Binary_Operator.COMP_GEQ): 

1574 return Value( 

1575 location = self.location, 

1576 value = { 

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

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

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

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

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

1582 typ = self.typ) 

1583 

1584 elif self.operator == Binary_Operator.STRING_CONTAINS: 

1585 assert isinstance(v_lhs.value, str) 

1586 assert isinstance(v_rhs.value, str) 

1587 

1588 return Value(location = self.location, 

1589 value = v_lhs.value in v_rhs.value, 

1590 typ = self.typ) 

1591 

1592 elif self.operator == Binary_Operator.STRING_STARTSWITH: 

1593 assert isinstance(v_lhs.value, str) 

1594 assert isinstance(v_rhs.value, str) 

1595 return Value(location = self.location, 

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

1597 typ = self.typ) 

1598 

1599 elif self.operator == Binary_Operator.STRING_ENDSWITH: 

1600 assert isinstance(v_lhs.value, str) 

1601 assert isinstance(v_rhs.value, str) 

1602 return Value(location = self.location, 

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

1604 typ = self.typ) 

1605 

1606 elif self.operator == Binary_Operator.STRING_REGEX: 

1607 assert isinstance(v_lhs.value, str) 

1608 assert isinstance(v_rhs.value, str) 

1609 return Value(location = self.location, 

1610 value = re.match(v_rhs.value, 

1611 v_lhs.value) is not None, 

1612 typ = self.typ) 

1613 

1614 elif self.operator == Binary_Operator.ARRAY_CONTAINS: 

1615 assert isinstance(v_rhs.value, list) 

1616 

1617 return Value(location = self.location, 

1618 value = v_lhs in v_rhs.value, 

1619 typ = self.typ) 

1620 

1621 elif self.operator == Binary_Operator.PLUS: 

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

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

1624 return Value(location = self.location, 

1625 value = v_lhs.value + v_rhs.value, 

1626 typ = self.typ) 

1627 

1628 elif self.operator == Binary_Operator.MINUS: 

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

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

1631 return Value(location = self.location, 

1632 value = v_lhs.value - v_rhs.value, 

1633 typ = self.typ) 

1634 

1635 elif self.operator == Binary_Operator.TIMES: 

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

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

1638 return Value(location = self.location, 

1639 value = v_lhs.value * v_rhs.value, 

1640 typ = self.typ) 

1641 

1642 elif self.operator == Binary_Operator.DIVIDE: 

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

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

1645 

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

1647 mh.error(v_rhs.location, 

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

1649 (self.to_string(), 

1650 mh.cross_file_reference(self.location))) 

1651 

1652 if isinstance(v_lhs.value, int): 

1653 return Value(location = self.location, 

1654 value = v_lhs.value // v_rhs.value, 

1655 typ = self.typ) 

1656 else: 

1657 return Value(location = self.location, 

1658 value = v_lhs.value / v_rhs.value, 

1659 typ = self.typ) 

1660 

1661 elif self.operator == Binary_Operator.REMAINDER: 

1662 assert isinstance(v_lhs.value, int) 

1663 assert isinstance(v_rhs.value, int) 

1664 

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

1666 mh.error(v_rhs.location, 

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

1668 (self.to_string(), 

1669 mh.cross_file_reference(self.location))) 

1670 

1671 return Value(location = self.location, 

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

1673 typ = self.typ) 

1674 

1675 elif self.operator == Binary_Operator.POWER: 

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

1677 assert isinstance(v_rhs.value, int) 

1678 return Value(location = self.location, 

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

1680 typ = self.typ) 

1681 

1682 elif self.operator == Binary_Operator.INDEX: 

1683 assert isinstance(v_lhs.value, list) 

1684 assert isinstance(v_rhs.value, int) 

1685 

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

1687 mh.error(v_rhs.location, 

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

1689 (self.to_string(), 

1690 mh.cross_file_reference(self.location))) 

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

1692 v_rhs.value > v_lhs.typ.upper_bound: 

1693 mh.error(v_rhs.location, 

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

1695 (v_lhs.typ.upper_bound, 

1696 self.to_string(), 

1697 mh.cross_file_reference(self.location))) 

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

1699 mh.error(v_lhs.location, 

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

1701 (self.to_string(), 

1702 mh.cross_file_reference(self.location))) 

1703 

1704 return Value(location = self.location, 

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

1706 typ = self.typ) 

1707 

1708 else: 

1709 mh.ice_loc(self.location, 

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

1711 

1712 def can_be_null(self): 

1713 return False 

1714 

1715 

1716class Field_Access_Expression(Expression): 

1717 """Tuple field access 

1718 

1719 For example in:: 

1720 

1721 foo.bar 

1722 ^1 ^2 

1723 

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

1725 :type: Expression 

1726 

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

1728 :type: Composite_Component 

1729 

1730 """ 

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

1732 assert isinstance(mh, Message_Handler) 

1733 assert isinstance(n_prefix, Expression) 

1734 assert isinstance(n_field, Composite_Component) 

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

1736 self.n_prefix = n_prefix 

1737 self.n_field = n_field 

1738 

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

1740 

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

1742 # lobster-exclude: Debugging feature 

1743 self.write_indent(indent, "Field_Access (%s)" % self.n_field.name) 

1744 self.n_prefix.dump(indent + 1) 

1745 

1746 def to_string(self): 

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

1748 

1749 def evaluate(self, mh, context): 

1750 assert isinstance(mh, Message_Handler) 

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

1752 

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

1754 

1755 def can_be_null(self): 

1756 return False 

1757 

1758 

1759class Range_Test(Expression): 

1760 """Range membership test 

1761 

1762 For example in:: 

1763 

1764 x in 1 .. field+1 

1765 ^lhs ^lower ^^^^^^^upper 

1766 

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

1768 you can have arbitrarily complex expressions here. 

1769 

1770 :attribute n_lhs: the expression to test 

1771 :type: Expression 

1772 

1773 :attribute n_lower: the lower bound 

1774 :type: Expression 

1775 

1776 :attribute n_lower: the upper bound 

1777 :type: Expression 

1778 

1779 """ 

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

1781 # lobster-trace: LRM.Relation 

1782 super().__init__(location, typ) 

1783 assert isinstance(mh, Message_Handler) 

1784 assert isinstance(n_lhs, Expression) 

1785 assert isinstance(n_lower, Expression) 

1786 assert isinstance(n_upper, Expression) 

1787 self.n_lhs = n_lhs 

1788 self.n_lower = n_lower 

1789 self.n_upper = n_upper 

1790 

1791 self.n_lhs.ensure_type(mh, Builtin_Numeric_Type) 

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

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

1794 

1795 def to_string(self): 

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

1797 self.n_lower.to_string(), 

1798 self.n_upper.to_string()) 

1799 

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

1801 # lobster-exclude: Debugging feature 

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

1803 self.write_indent(indent + 1, "Type: %s" % self.typ) 

1804 self.n_lhs.dump(indent + 1) 

1805 self.n_lower.dump(indent + 1) 

1806 self.n_upper.dump(indent + 1) 

1807 

1808 def evaluate(self, mh, context): 

1809 # lobster-trace: LRM.Null_Is_Invalid 

1810 assert isinstance(mh, Message_Handler) 

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

1812 

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

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

1815 mh.error(v_lhs.location, 

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

1817 (self.to_string(), 

1818 mh.cross_file_reference(self.location))) 

1819 

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

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

1822 mh.error(v_lower.location, 

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

1824 (self.to_string(), 

1825 mh.cross_file_reference(self.location))) 

1826 

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

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

1829 mh.error(v_upper.location, 

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

1831 (self.to_string(), 

1832 mh.cross_file_reference(self.location))) 

1833 

1834 return Value(location = self.location, 

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

1836 typ = self.typ) 

1837 

1838 def can_be_null(self): 

1839 return False 

1840 

1841 

1842class OneOf_Expression(Expression): 

1843 """OneOf expression 

1844 

1845 For example in:: 

1846 

1847 oneof(a, b, c) 

1848 ^^^^^^^ choices 

1849 

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

1851 :type: list[Expression] 

1852 """ 

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

1854 # lobster-trace: LRM.Signature_OneOf 

1855 super().__init__(location, typ) 

1856 assert isinstance(typ, Builtin_Boolean) 

1857 assert isinstance(mh, Message_Handler) 

1858 assert isinstance(choices, list) 

1859 assert all(isinstance(item, Expression) 

1860 for item in choices) 

1861 self.choices = choices 

1862 

1863 for n_choice in choices: 

1864 n_choice.ensure_type(mh, Builtin_Boolean) 

1865 

1866 def to_string(self): 

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

1868 for n_choice in self.choices) 

1869 

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

1871 # lobster-exclude: Debugging feature 

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

1873 self.write_indent(indent + 1, "Type: %s" % self.typ) 

1874 for n_choice in self.choices: 

1875 n_choice.dump(indent + 1) 

1876 

1877 def evaluate(self, mh, context): 

1878 # lobster-trace: LRM.OneOf_Semantics 

1879 assert isinstance(mh, Message_Handler) 

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

1881 

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

1883 for n_choice in self.choices] 

1884 

1885 return Value(location = self.location, 

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

1887 typ = self.typ) 

1888 

1889 def can_be_null(self): 

1890 return False 

1891 

1892 

1893class Action(Node): 

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

1895 

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

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

1898 Actions:: 

1899 

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

1901 ^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^ 

1902 

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

1904 the :class:`Conditional_Expression` itself. 

1905 

1906 :attribute kind: Either if or elseif 

1907 :type: str 

1908 

1909 :attribute n_cond: The boolean condition expression 

1910 :type: Expression 

1911 

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

1913 :type: Expression 

1914 

1915 """ 

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

1917 # lobster-trace: LRM.Conditional_Expression 

1918 assert isinstance(mh, Message_Handler) 

1919 assert isinstance(t_kind, Token) 

1920 assert t_kind.kind == "KEYWORD" 

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

1922 assert isinstance(n_condition, Expression) 

1923 assert isinstance(n_expression, Expression) 

1924 super().__init__(t_kind.location) 

1925 self.kind = t_kind.value 

1926 self.n_cond = n_condition 

1927 self.n_expr = n_expression 

1928 # lobster-trace: LRM.Conditional_Expression_Types 

1929 self.n_cond.ensure_type(mh, Builtin_Boolean) 

1930 

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

1932 # lobster-exclude: Debugging feature 

1933 self.write_indent(indent, "%s Action" % self.kind.capitalize()) 

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

1935 self.n_cond.dump(indent + 2) 

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

1937 self.n_expr.dump(indent + 2) 

1938 

1939 def to_string(self): 

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

1941 self.n_cond.to_string(), 

1942 self.n_expr.to_string()) 

1943 

1944 

1945class Conditional_Expression(Expression): 

1946 """A conditional expression 

1947 

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

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

1950 expression with two Actions:: 

1951 

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

1953 ^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^ 

1954 

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

1956 

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

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

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

1960 of all actions and the else expression will match. 

1961 

1962 :attribute actions: a list of Actions 

1963 :type: list[Action] 

1964 

1965 :attribute else_expr: the else expression 

1966 :type: Expression 

1967 

1968 """ 

1969 def __init__(self, location, if_action): 

1970 # lobster-trace: LRM.Conditional_Expression 

1971 assert isinstance(if_action, Action) 

1972 assert if_action.kind == "if" 

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

1974 self.actions = [if_action] 

1975 self.else_expr = None 

1976 

1977 def add_elsif(self, mh, n_action): 

1978 # lobster-trace: LRM.Conditional_Expression 

1979 # lobster-trace; LRM.Conditional_Expression_Types 

1980 assert isinstance(mh, Message_Handler) 

1981 assert isinstance(n_action, Action) 

1982 assert n_action.kind == "elsif" 

1983 

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

1985 self.actions.append(n_action) 

1986 

1987 def set_else_part(self, mh, n_expr): 

1988 # lobster-trace: LRM.Conditional_Expression 

1989 # lobster-trace; LRM.Conditional_Expression_Types 

1990 assert isinstance(mh, Message_Handler) 

1991 assert isinstance(n_expr, Expression) 

1992 

1993 n_expr.ensure_type(mh, self.typ) 

1994 self.else_expr = n_expr 

1995 

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

1997 # lobster-exclude: Debugging feature 

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

1999 for action in self.actions: 

2000 action.dump(indent + 1) 

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

2002 self.else_expr.dump(indent + 2) 

2003 

2004 def to_string(self): 

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

2006 for action in self.actions) 

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

2008 rv += ")" 

2009 return rv 

2010 

2011 def evaluate(self, mh, context): 

2012 # lobster-trace: LRM.Conditional_Expression_Else 

2013 # lobster-trace: LRM.Conditional_Expression_Evaluation 

2014 # lobster-trace: LRM.Null_Is_Invalid 

2015 assert isinstance(mh, Message_Handler) 

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

2017 

2018 for action in self.actions: 

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

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

2021 mh.error(v_cond.location, 

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

2023 (action.to_string(), 

2024 mh.cross_file_reference(self.location))) 

2025 if v_cond.value: 

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

2027 

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

2029 

2030 def can_be_null(self): 

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

2032 return True 

2033 

2034 return any(action.n_expr.can_be_null() 

2035 for action in self.actions) 

2036 

2037 

2038class Quantified_Expression(Expression): 

2039 """A quantified expression 

2040 

2041 For example:: 

2042 

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

2044 ^4 ^1 ^2 ^^^^^3 

2045 

2046 A quantified expression introduces and binds a 

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

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

2049 each component of the source in turn. 

2050 

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

2052 :type: Quantified_Variable 

2053 

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

2055 :type: Name_Reference 

2056 

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

2058 :type: Expression 

2059 

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

2061 :type: Boolean 

2062 

2063 """ 

2064 def __init__(self, mh, location, 

2065 typ, 

2066 universal, 

2067 n_variable, 

2068 n_source, 

2069 n_expr): 

2070 # lobster-trace: LRM.Quantified_Expression 

2071 # lobster-trace: LRM.Quantification_Type 

2072 super().__init__(location, typ) 

2073 assert isinstance(typ, Builtin_Boolean) 

2074 assert isinstance(universal, bool) 

2075 assert isinstance(n_variable, Quantified_Variable) 

2076 assert isinstance(n_expr, Expression) 

2077 assert isinstance(n_source, Name_Reference) 

2078 self.universal = universal 

2079 self.n_var = n_variable 

2080 self.n_expr = n_expr 

2081 self.n_source = n_source 

2082 self.n_expr.ensure_type(mh, Builtin_Boolean) 

2083 

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

2085 # lobster-exclude: Debugging feature 

2086 if self.universal: 

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

2088 else: 

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

2090 self.n_var.dump(indent + 1) 

2091 self.n_expr.dump(indent + 1) 

2092 

2093 def to_string(self): 

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

2095 if self.universal 

2096 else "exists", 

2097 self.n_var.name, 

2098 self.n_source.to_string(), 

2099 self.n_expr.to_string()) 

2100 

2101 def evaluate(self, mh, context): 

2102 # lobster-trace: LRM.Null_Is_Invalid 

2103 # lobster-trace: LRM.Universal_Quantification_Semantics 

2104 # lobster-trace: LRM.Existential_Quantification_Semantics 

2105 assert isinstance(mh, Message_Handler) 

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

2107 

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

2109 new_ctx = {} 

2110 else: 

2111 new_ctx = copy(context) 

2112 

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

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

2115 # error messages. 

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

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

2118 if isinstance(array_values, Implicit_Null): 

2119 mh.error(array_values.location, 

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

2121 "must not be null" % 

2122 (self.n_source.to_string(), 

2123 self.to_string(), 

2124 mh.cross_file_reference(self.location))) 

2125 else: 

2126 assert isinstance(array_values, Array_Aggregate) 

2127 

2128 rv = self.universal 

2129 loc = self.location 

2130 for binding in array_values.value: 

2131 new_ctx[self.n_var.name] = binding 

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

2133 assert isinstance(result.value, bool) 

2134 if self.universal and not result.value: 

2135 rv = False 

2136 loc = binding.location 

2137 break 

2138 elif not self.universal and result.value: 

2139 rv = True 

2140 loc = binding.location 

2141 break 

2142 

2143 return Value(location = loc, 

2144 value = rv, 

2145 typ = self.typ) 

2146 

2147 def can_be_null(self): 

2148 return False 

2149 

2150 

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

2152# AST Nodes (Entities) 

2153############################################################################## 

2154 

2155class Entity(Node, metaclass=ABCMeta): 

2156 """Base class for all entities. 

2157 

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

2159 allocate memory. Examples of entities are types and record 

2160 objects. 

2161 

2162 :attribute name: unqualified name of the entity 

2163 :type: str 

2164 

2165 """ 

2166 def __init__(self, name, location): 

2167 # lobster-trace: LRM.Described_Name_Equality 

2168 super().__init__(location) 

2169 assert isinstance(name, str) 

2170 self.name = name 

2171 

2172 

2173class Typed_Entity(Entity, metaclass=ABCMeta): 

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

2175 

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

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

2178 are record objects and components. 

2179 

2180 :attribute n_typ: type of the entity 

2181 :type: Type 

2182 

2183 """ 

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

2185 # lobster-exclude: Constructor only declares variables 

2186 super().__init__(name, location) 

2187 assert isinstance(n_typ, Type) 

2188 self.n_typ = n_typ 

2189 

2190 

2191class Quantified_Variable(Typed_Entity): 

2192 """Variable used in quantified expression. 

2193 

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

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

2196 

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

2198 ^ 

2199 

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

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

2202 

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

2204 :type: Type 

2205 

2206 """ 

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

2208 # lobster-exclude: Debugging feature 

2209 self.write_indent(indent, "Quantified Variable %s" % self.name) 

2210 self.n_typ.dump(indent + 1) 

2211 

2212 

2213class Type(Entity, metaclass=ABCMeta): 

2214 """Abstract base class for all types. 

2215 

2216 """ 

2217 def perform_type_checks(self, mh, value): 

2218 assert isinstance(mh, Message_Handler) 

2219 assert isinstance(value, Expression) 

2220 return True 

2221 

2222 def get_example_value(self): 

2223 # lobster-exclude: utility method 

2224 assert False 

2225 

2226 

2227class Concrete_Type(Type, metaclass=ABCMeta): 

2228 # lobster-trace: LRM.Type_Declarations 

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

2230 

2231 :attribute n_package: package where this type was declared 

2232 :type: Package 

2233 """ 

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

2235 super().__init__(name, location) 

2236 assert isinstance(n_package, Package) 

2237 self.n_package = n_package 

2238 

2239 def fully_qualified_name(self): 

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

2241 

2242 :returns: the type's full name 

2243 :rtype: str 

2244 """ 

2245 return self.n_package.name + "." + self.name 

2246 

2247 def __hash__(self): 

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

2249 

2250 def __repr__(self): 

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

2252 self.fully_qualified_name()) 

2253 

2254 

2255class Builtin_Type(Type, metaclass=ABCMeta): 

2256 # lobster-trace: LRM.Builtin_Types 

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

2258 

2259 """ 

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

2261 

2262 def __init__(self, name): 

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

2264 

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

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

2267 

2268 

2269class Builtin_Numeric_Type(Builtin_Type, metaclass=ABCMeta): 

2270 # lobster-trace: LRM.Builtin_Types 

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

2272 

2273 """ 

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

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

2276 

2277 

2278class Builtin_Function(Entity): 

2279 # lobster-trace: LRM.Builtin_Functions 

2280 """Builtin functions. 

2281 

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

2283 

2284 :attribute arity: number of parameters 

2285 :type: int 

2286 

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

2288 :type: bool 

2289 

2290 """ 

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

2292 

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

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

2295 assert isinstance(arity, int) 

2296 assert isinstance(arity_at_least, bool) 

2297 assert arity >= 0 

2298 self.arity = arity 

2299 self.arity_at_least = arity_at_least 

2300 

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

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

2303 

2304 

2305class Array_Type(Type): 

2306 """Anonymous array type. 

2307 

2308 These are declared implicitly for each record component that has 

2309 an array specifier:: 

2310 

2311 foo Integer [5 .. *] 

2312 ^ 

2313 

2314 :attribute lower_bound: minimum number of elements 

2315 :type: int 

2316 

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

2318 :type: Location 

2319 

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

2321 :type: int 

2322 

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

2324 :type: Location 

2325 

2326 :attribute element_type: type of the array elements 

2327 :type: Type 

2328 

2329 """ 

2330 def __init__(self, 

2331 location, 

2332 element_type, 

2333 loc_lower, 

2334 lower_bound, 

2335 loc_upper, 

2336 upper_bound): 

2337 # lobster-exclude: Constructor only declares variables 

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

2339 assert isinstance(lower_bound, int) 

2340 assert lower_bound >= 0 

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

2342 assert upper_bound is None or upper_bound >= 0 

2343 assert isinstance(loc_lower, Location) 

2344 assert isinstance(loc_upper, Location) 

2345 

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

2347 name = "universal array" 

2348 elif upper_bound is None: 

2349 if lower_bound == 0: 

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

2351 else: 

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

2353 element_type.name) 

2354 elif lower_bound == upper_bound: 

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

2356 element_type.name) 

2357 else: 

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

2359 upper_bound, 

2360 element_type.name) 

2361 super().__init__(name, location) 

2362 self.lower_bound = lower_bound 

2363 self.loc_lower = loc_lower 

2364 self.upper_bound = upper_bound 

2365 self.loc_upper = loc_upper 

2366 self.element_type = element_type 

2367 

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

2369 # lobster-exclude: Debugging feature 

2370 self.write_indent(indent, "Array_Type") 

2371 self.write_indent(indent + 1, "Lower bound: %u" % self.lower_bound) 

2372 if self.upper_bound is None: 

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

2374 else: 

2375 self.write_indent(indent + 1, "Upper bound: %u" % self.upper_bound) 

2376 self.write_indent(indent + 1, "Element type: %s" % 

2377 self.element_type.name) 

2378 

2379 def perform_type_checks(self, mh, value): 

2380 assert isinstance(mh, Message_Handler) 

2381 if isinstance(value, Array_Aggregate): 

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

2383 for v in value.value) 

2384 else: 

2385 assert isinstance(value, Implicit_Null) 

2386 return True 

2387 

2388 def get_example_value(self): 

2389 # lobster-exclude: utility method 

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

2391 

2392 

2393class Builtin_Integer(Builtin_Numeric_Type): 

2394 # lobster-trace: LRM.Builtin_Types 

2395 # lobster-trace: LRM.Integer_Values 

2396 """Builtin integer type.""" 

2397 def __init__(self): 

2398 super().__init__("Integer") 

2399 

2400 def get_example_value(self): 

2401 # lobster-exclude: utility method 

2402 return "100" 

2403 

2404 

2405class Builtin_Decimal(Builtin_Numeric_Type): 

2406 # lobster-trace: LRM.Builtin_Types 

2407 # lobster-trace: LRM.Decimal_Values 

2408 """Builtin decimal type.""" 

2409 def __init__(self): 

2410 super().__init__("Decimal") 

2411 

2412 def get_example_value(self): 

2413 # lobster-exclude: utility method 

2414 return "3.14" 

2415 

2416 

2417class Builtin_Boolean(Builtin_Type): 

2418 # lobster-trace: LRM.Builtin_Types 

2419 # lobster-trace: LRM.Boolean_Values 

2420 """Builtin boolean type.""" 

2421 def __init__(self): 

2422 super().__init__("Boolean") 

2423 

2424 def get_example_value(self): 

2425 # lobster-exclude: utility method 

2426 return "true" 

2427 

2428 

2429class Builtin_String(Builtin_Type): 

2430 # lobster-trace: LRM.Builtin_Types 

2431 # lobster-trace: LRM.String_Values 

2432 """Builtin string type.""" 

2433 def __init__(self): 

2434 super().__init__("String") 

2435 

2436 def get_example_value(self): 

2437 # lobster-exclude: utility method 

2438 return "\"potato\"" 

2439 

2440 

2441class Builtin_Markup_String(Builtin_String): 

2442 # lobster-trace: LRM.Builtin_Types 

2443 # lobster-trace: LRM.Markup_String_Values 

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

2445 objects. 

2446 """ 

2447 def __init__(self): 

2448 super().__init__() 

2449 self.name = "Markup_String" 

2450 

2451 def get_example_value(self): 

2452 # lobster-exclude: utility method 

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

2454 

2455 

2456class Package(Entity): 

2457 """Packages. 

2458 

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

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

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

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

2463 

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

2465 trlc file 

2466 :type: bool 

2467 

2468 :attribute symbols: symbol table of the package 

2469 :type: Symbol_Table 

2470 

2471 """ 

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

2473 # lobster-exclude: Constructor only declares variables 

2474 super().__init__(name, location) 

2475 assert isinstance(builtin_stab, Symbol_Table) 

2476 assert isinstance(declared_late, bool) 

2477 self.symbols = Symbol_Table() 

2478 self.symbols.make_visible(builtin_stab) 

2479 self.declared_late = declared_late 

2480 

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

2482 # lobster-exclude: Debugging feature 

2483 self.write_indent(indent, "Package %s" % self.name) 

2484 self.write_indent(indent + 1, "Declared_Late: %s" % self.declared_late) 

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

2486 

2487 def __repr__(self): 

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

2489 self.name) 

2490 

2491 

2492class Composite_Type(Concrete_Type, metaclass=ABCMeta): 

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

2494 functionality. 

2495 

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

2497 :type: Symbol_Table[Composite_Component] 

2498 

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

2500 :type: str 

2501 

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

2503 inherited checks) 

2504 :type: list[Check] 

2505 

2506 """ 

2507 def __init__(self, 

2508 name, 

2509 description, 

2510 location, 

2511 package, 

2512 inherited_symbols=None): 

2513 # lobster-trace: LRM.Described_Name_Description 

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

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

2516 assert isinstance(inherited_symbols, Symbol_Table) or \ 

2517 inherited_symbols is None 

2518 

2519 self.components = Symbol_Table(inherited_symbols) 

2520 self.description = description 

2521 self.checks = [] 

2522 

2523 def add_check(self, n_check): 

2524 # lobster-trace: LRM.Check_Evaluation_Order 

2525 assert isinstance(n_check, Check) 

2526 self.checks.append(n_check) 

2527 

2528 def iter_checks(self): 

2529 # lobster-trace: LRM.Check_Evaluation_Order 

2530 yield from self.checks 

2531 

2532 def all_components(self): 

2533 # lobster-exclude: Convenience function 

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

2535 

2536 :rtype: list[Composite_Component] 

2537 """ 

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

2539 

2540 

2541class Composite_Component(Typed_Entity): 

2542 """Component in a record or tuple. 

2543 

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

2545 declared:: 

2546 

2547 type|tuple T { 

2548 foo "blah" optional Boolean 

2549 ^1 ^2 ^3 ^4 

2550 

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

2552 :type: str 

2553 

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

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

2556 :type: Composite_Type 

2557 

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

2559 :type: bool 

2560 

2561 """ 

2562 

2563 def __init__(self, 

2564 name, 

2565 description, 

2566 location, 

2567 member_of, 

2568 n_typ, 

2569 optional): 

2570 # lobster-trace: LRM.Described_Name_Description 

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

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

2573 assert isinstance(member_of, Composite_Type) 

2574 assert isinstance(optional, bool) 

2575 self.description = description 

2576 self.member_of = member_of 

2577 self.optional = optional 

2578 

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

2580 # lobster-exclude: Debugging feature 

2581 self.write_indent(indent, "Composite_Component %s" % self.name) 

2582 if self.description: 

2583 self.write_indent(indent + 1, "Description: %s" % self.description) 

2584 self.write_indent(indent + 1, "Optional: %s" % self.optional) 

2585 self.write_indent(indent + 1, "Type: %s" % self.n_typ.name) 

2586 

2587 def __repr__(self): 

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

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

2590 self.name) 

2591 

2592 

2593class Record_Type(Composite_Type): 

2594 """A user-defined record type. 

2595 

2596 In this example:: 

2597 

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

2599 ^1 ^2 ^3 

2600 

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

2602 of the :class:`Composite_Type` base. 

2603 

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

2605 :type: Record_Type 

2606 

2607 :attribute frozen: mapping of frozen components 

2608 :type: dict[str, Expression] 

2609 

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

2611 :type: bool 

2612 

2613 :attribute is_abstract: type is abstract 

2614 :type: bool 

2615 

2616 """ 

2617 def __init__(self, 

2618 name, 

2619 description, 

2620 location, 

2621 package, 

2622 n_parent, 

2623 is_abstract): 

2624 # lobster-exclude: Constructor only declares variables 

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

2626 assert isinstance(is_abstract, bool) 

2627 super().__init__(name, 

2628 description, 

2629 location, 

2630 package, 

2631 n_parent.components if n_parent else None) 

2632 self.parent = n_parent 

2633 self.frozen = {} 

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

2635 self.is_abstract = is_abstract 

2636 

2637 def iter_checks(self): 

2638 # lobster-trace: LRM.Check_Evaluation_Order 

2639 # lobster-trace: LRM.Check_Evaluation_Order_For_Extensions 

2640 if self.parent: 

2641 yield from self.parent.iter_checks() 

2642 yield from self.checks 

2643 

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

2645 # lobster-exclude: Debugging feature 

2646 self.write_indent(indent, "Record_Type %s" % self.name) 

2647 if self.description: 

2648 self.write_indent(indent + 1, "Description: %s" % self.description) 

2649 if self.parent: 

2650 self.write_indent(indent + 1, "Parent: %s" % self.parent.name) 

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

2652 if self.checks: 

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

2654 for n_check in self.checks: 

2655 n_check.dump(indent + 2) 

2656 else: 

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

2658 

2659 def all_components(self): 

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

2661 

2662 :rtype: list[Composite_Component] 

2663 """ 

2664 if self.parent: 

2665 return self.parent.all_components() + \ 

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

2667 else: 

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

2669 

2670 def is_subclass_of(self, record_type): 

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

2672 

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

2674 :type record_type: Record_Type 

2675 

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

2677 :rtype: Boolean 

2678 """ 

2679 assert isinstance(record_type, Record_Type) 

2680 

2681 ptr = self 

2682 while ptr: 

2683 if ptr is record_type: 

2684 return True 

2685 else: 

2686 ptr = ptr.parent 

2687 return False 

2688 

2689 def is_frozen(self, n_component): 

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

2691 

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

2693 (or any of its parents) 

2694 :type n_component: Composite_Component 

2695 

2696 :rtype: bool 

2697 """ 

2698 assert isinstance(n_component, Composite_Component) 

2699 if n_component.name in self.frozen: 

2700 return True 

2701 elif self.parent: 

2702 return self.parent.is_frozen(n_component) 

2703 else: 

2704 return False 

2705 

2706 def get_freezing_expression(self, n_component): 

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

2708 

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

2710 component that his not frozen. 

2711 

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

2713 (or any of its parents) 

2714 :type n_component: Composite_Component 

2715 

2716 :rtype: Expression 

2717 

2718 """ 

2719 assert isinstance(n_component, Composite_Component) 

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

2721 return self.frozen[n_component.name] 

2722 elif self.parent: 

2723 return self.parent.get_freezing_expression(n_component) 

2724 else: 

2725 assert False 

2726 

2727 def get_example_value(self): 

2728 # lobster-exclude: utility method 

2729 return "%s_instance" % self.name 

2730 

2731 

2732class Tuple_Type(Composite_Type): 

2733 """A user-defined tuple type. 

2734 

2735 In this example:: 

2736 

2737 tuple T "optional description of T" { 

2738 ^1 ^2 

2739 

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

2741 of the :class:`Composite_Type` base. 

2742 

2743 :attribute separators: list of syntactic separators. 

2744 :type: list[Separator] 

2745 

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

2747 precisely one less separator than components. 

2748 

2749 """ 

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

2751 # lobster-trace: LRM.Tuple_Declaration 

2752 super().__init__(name, 

2753 description, 

2754 location, 

2755 package) 

2756 self.separators = [] 

2757 

2758 def add_separator(self, n_separator): 

2759 # lobster-exclude: utility method 

2760 assert isinstance(n_separator, Separator) 

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

2762 self.separators.append(n_separator) 

2763 

2764 def iter_separators(self): 

2765 """Iterate over all separators""" 

2766 # lobster-exclude: utility method 

2767 yield from self.separators 

2768 

2769 def iter_sequence(self): 

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

2771 # lobster-exclude: utility method 

2772 if self.separators: 

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

2774 yield n_component 

2775 if i < len(self.separators): 

2776 yield self.separators[i] 

2777 else: 

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

2779 

2780 def has_separators(self): 

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

2782 # lobster-exclude: utility method 

2783 return bool(self.separators) 

2784 

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

2786 # lobster-exclude: Debugging feature 

2787 self.write_indent(indent, "Tuple_Type %s" % self.name) 

2788 if self.description: 

2789 self.write_indent(indent + 1, "Description: %s" % self.description) 

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

2791 for n_item in self.iter_sequence(): 

2792 n_item.dump(indent + 2) 

2793 if self.checks: 

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

2795 for n_check in self.checks: 

2796 n_check.dump(indent + 2) 

2797 else: 

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

2799 

2800 def perform_type_checks(self, mh, value): 

2801 # lobster-trace: LRM.Check_Evaluation_Order 

2802 assert isinstance(mh, Message_Handler) 

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

2804 ok = True 

2805 for check in self.iter_checks(): 

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

2807 ok = False 

2808 return ok 

2809 else: 

2810 assert isinstance(value, Implicit_Null) 

2811 return True 

2812 

2813 def get_example_value(self): 

2814 # lobster-exclude: utility method 

2815 parts = [] 

2816 for n_item in self.iter_sequence(): 

2817 if isinstance(n_item, Composite_Component): 

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

2819 else: 

2820 parts.append(n_item.to_string()) 

2821 if self.has_separators(): 

2822 return " ".join(parts) 

2823 else: 

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

2825 

2826 

2827class Separator(Node): 

2828 # lobster-trace: LRM.Tuple_Declaration 

2829 """User-defined syntactic separator 

2830 

2831 For example:: 

2832 

2833 separator x 

2834 ^1 

2835 

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

2837 :type: Token 

2838 """ 

2839 def __init__(self, token): 

2840 super().__init__(token.location) 

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

2842 "AT", 

2843 "COLON", 

2844 "SEMICOLON") 

2845 self.token = token 

2846 

2847 def to_string(self): 

2848 return { 

2849 "AT" : "@", 

2850 "COLON" : ":", 

2851 "SEMICOLON" : ";" 

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

2853 

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

2855 self.write_indent(indent, "Separator %s" % self.token.value) 

2856 

2857 

2858class Enumeration_Type(Concrete_Type): 

2859 """User-defined enumeration types. 

2860 

2861 For example:: 

2862 

2863 enum T "potato" { 

2864 ^1 ^2 

2865 

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

2867 :type: str 

2868 

2869 :attribute literals: the literals in this enumeration 

2870 :type: Symbol_Table[Enumeration_Literal_Spec] 

2871 

2872 """ 

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

2874 # lobster-trace: LRM.Described_Name_Description 

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

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

2877 self.literals = Symbol_Table() 

2878 self.description = description 

2879 

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

2881 # lobster-exclude: Debugging feature 

2882 self.write_indent(indent, "Enumeration_Type %s" % self.name) 

2883 if self.description: 

2884 self.write_indent(indent + 1, "Description: %s" % self.description) 

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

2886 

2887 def get_example_value(self): 

2888 # lobster-exclude: utility method 

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

2890 if options: 

2891 choice = len(options) // 2 

2892 return self.name + "." + choice.name 

2893 else: 

2894 return "ERROR" 

2895 

2896 

2897class Enumeration_Literal_Spec(Typed_Entity): 

2898 """Declared literal in an enumeration declaration. 

2899 

2900 Note that for literals mentioned later in record object 

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

2902 are used here:: 

2903 

2904 enum ASIL { 

2905 QM "not safety related" 

2906 ^1 ^2 

2907 

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

2909 :type: str 

2910 

2911 """ 

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

2913 # lobster-trace: LRM.Described_Name_Description 

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

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

2916 assert isinstance(enum, Enumeration_Type) 

2917 self.description = description 

2918 

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

2920 # lobster-exclude: Debugging feature 

2921 self.write_indent(indent, "Enumeration_Literal_Spec %s" % self.name) 

2922 if self.description: 

2923 self.write_indent(indent + 1, "Description: %s" % self.description) 

2924 

2925 

2926class Record_Object(Typed_Entity): 

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

2928 

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

2930 

2931 section "Potato" { 

2932 ^5 

2933 Requirement PotatoReq { 

2934 ^1 ^2 

2935 component1 = 42 

2936 ^3 ^4 

2937 

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

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

2940 class. 

2941 

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

2943 :type: dict[str, Expression] 

2944 

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

2946 :type: Section 

2947 

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

2949 :type: Section 

2950 

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

2952 to: 

2953 

2954 * :class:`Literal` 

2955 * :class:`Unary_Expression` 

2956 * :class:`Array_Aggregate` 

2957 * :class:`Tuple_Aggregate` 

2958 * :class:`Record_Reference` 

2959 * :class:`Implicit_Null` 

2960 

2961 """ 

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

2963 # lobster-trace: LRM.Section_Declaration 

2964 # lobster-trace: LRM.Unspecified_Optional_Components 

2965 # lobster-trace: LRM.Record_Object_Declaration 

2966 

2967 assert isinstance(n_typ, Record_Type) 

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

2969 assert isinstance(n_package, Package) 

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

2971 self.field = { 

2972 comp.name: Implicit_Null(self, comp) 

2973 for comp in self.n_typ.all_components() 

2974 } 

2975 self.section = section 

2976 self.n_package = n_package 

2977 

2978 def fully_qualified_name(self): 

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

2980 

2981 :returns: the object's full name 

2982 :rtype: str 

2983 """ 

2984 return self.n_package.name + "." + self.name 

2985 

2986 def to_python_dict(self): 

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

2988 

2989 For example it might provide:: 

2990 

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

2992 "bar" : None, 

2993 "baz" : "value"} 

2994 

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

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

2997 

2998 """ 

2999 return {name: value.to_python_object() 

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

3001 

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

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

3004 

3005 def assign(self, component, value): 

3006 assert isinstance(component, Composite_Component) 

3007 assert isinstance(value, (Literal, 

3008 Array_Aggregate, 

3009 Tuple_Aggregate, 

3010 Record_Reference, 

3011 Implicit_Null, 

3012 Unary_Expression)), \ 

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

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

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

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

3017 self.field[component.name] = value 

3018 

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

3020 # lobster-exclude: Debugging feature 

3021 self.write_indent(indent, "Record_Object %s" % self.name) 

3022 self.write_indent(indent + 1, "Type: %s" % self.n_typ.name) 

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

3024 self.write_indent(indent + 1, "Field %s" % key) 

3025 value.dump(indent + 2) 

3026 if self.section: 

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

3028 

3029 def resolve_references(self, mh): 

3030 assert isinstance(mh, Message_Handler) 

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

3032 val.resolve_references(mh) 

3033 

3034 def perform_checks(self, mh): 

3035 # lobster-trace: LRM.Check_Evaluation_Order 

3036 # lobster-trace: LRM.Evaluation_Of_Checks 

3037 assert isinstance(mh, Message_Handler) 

3038 

3039 ok = True 

3040 

3041 # First evaluate all tuple checks 

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

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

3044 self.field[n_comp.name]): 

3045 ok = False 

3046 

3047 # Then evaluate all record checks 

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

3049 # Prints messages, if applicable. Raises exception on 

3050 # fatal checks, which causes this to abort. 

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

3052 ok = False 

3053 

3054 return ok 

3055 

3056 def __repr__(self): 

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

3058 self.n_package.name + "." + 

3059 self.n_typ.name + "." + 

3060 self.name) 

3061 

3062 

3063class Section(Entity): 

3064 # lobster-trace: LRM.Section_Declaration 

3065 """A section for readability 

3066 

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

3068 objects together:: 

3069 

3070 section "Foo" { 

3071 ^^^^^ parent section 

3072 section "Bar" { 

3073 ^^^^^ section 

3074 

3075 :attribute parent: the parent section or None 

3076 :type: Section 

3077 

3078 """ 

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

3080 super().__init__(name, location) 

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

3082 self.parent = parent 

3083 

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

3085 self.write_indent(indent, "Section %s" % self.name) 

3086 if self.parent is None: 

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

3088 else: 

3089 self.write_indent(indent + 1, "Parent: %s" % self.parent.name) 

3090 

3091 

3092############################################################################## 

3093# Symbol Table & Scopes 

3094############################################################################## 

3095 

3096class Symbol_Table: 

3097 """ Symbol table mapping names to entities 

3098 """ 

3099 def __init__(self, parent=None): 

3100 # lobster-exclude: Constructor only declares variables 

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

3102 self.parent = parent 

3103 self.imported = [] 

3104 self.table = OrderedDict() 

3105 self.trlc_files = [] 

3106 self.section_names = [] 

3107 

3108 @staticmethod 

3109 def simplified_name(name): 

3110 # lobster-trace: LRM.Sufficiently_Distinct 

3111 assert isinstance(name, str) 

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

3113 

3114 def all_names(self): 

3115 # lobster-exclude: API for users 

3116 """ All names in the symbol table 

3117 

3118 :rtype: set[str] 

3119 """ 

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

3121 if self.parent: 

3122 rv |= self.parent.all_names() 

3123 return rv 

3124 

3125 def iter_record_objects_by_section(self): 

3126 """API for users 

3127 

3128 Retriving information about the section hierarchy for record objects 

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

3130 sub sections and record objects 

3131 Output: Information about sections and level of sections, 

3132 record objects and levels of record object 

3133 """ 

3134 for record_object in self.iter_record_objects(): 

3135 location = record_object.location.file_name 

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

3137 self.trlc_files.append(location) 

3138 yield location 

3139 if record_object.section: 

3140 object_level = len(record_object.section) - 1 

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

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

3143 self.section_names.append(section) 

3144 yield section.name, level 

3145 yield record_object, object_level 

3146 else: 

3147 object_level = 0 

3148 yield record_object, object_level 

3149 

3150 def iter_record_objects(self): 

3151 # lobster-exclude: API for users 

3152 """ Iterate over all record objects 

3153 

3154 :rtype: iterable[Record_Object] 

3155 """ 

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

3157 if isinstance(item, Package): 

3158 yield from item.symbols.iter_record_objects() 

3159 

3160 elif isinstance(item, Record_Object): 

3161 yield item 

3162 

3163 def values(self, subtype=None): 

3164 # lobster-exclude: API for users 

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

3166 if self.parent: 

3167 yield from self.parent.values(subtype) 

3168 for name in sorted(self.table): 

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

3170 yield self.table[name] 

3171 

3172 def make_visible(self, stab): 

3173 assert isinstance(stab, Symbol_Table) 

3174 self.imported.append(stab) 

3175 

3176 def register(self, mh, entity): 

3177 # lobster-trace: LRM.Duplicate_Types 

3178 # lobster-trace: LRM.Unique_Enumeration_Literals 

3179 # lobster-trace: LRM.Tuple_Unique_Field_Names 

3180 # lobster-trace: LRM.Sufficiently_Distinct 

3181 # lobster-trace: LRM.Unique_Object_Names 

3182 

3183 assert isinstance(mh, Message_Handler) 

3184 assert isinstance(entity, Entity) 

3185 

3186 simple_name = self.simplified_name(entity.name) 

3187 

3188 if self.contains_raw(simple_name): 

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

3190 simplified=True) 

3191 if pdef.name == entity.name: 

3192 mh.error(entity.location, 

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

3194 mh.cross_file_reference(pdef.location)) 

3195 else: 

3196 mh.error(entity.location, 

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

3198 (entity.name, 

3199 pdef.name, 

3200 mh.cross_file_reference(pdef.location))) 

3201 

3202 else: 

3203 self.table[simple_name] = entity 

3204 

3205 def __contains__(self, name): 

3206 # lobster-trace: LRM.Described_Name_Equality 

3207 return self.contains(name) 

3208 

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

3210 # lobster-trace: LRM.Described_Name_Equality 

3211 # lobster-trace: LRM.Sufficiently_Distinct 

3212 # 

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

3214 # table. 

3215 assert isinstance(simple_name, str) 

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

3217 

3218 if simple_name in self.table: 

3219 # No need to continue searching since registering a 

3220 # clashing name would have been stopped 

3221 return precise_name is None or \ 

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

3223 

3224 elif self.parent: 

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

3226 

3227 for stab in self.imported: 

3228 if stab.contains_raw(simple_name, precise_name): 

3229 return True 

3230 

3231 return False 

3232 

3233 def contains(self, name): 

3234 # lobster-trace: LRM.Described_Name_Equality 

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

3236 

3237 :param name: the name to test 

3238 :type name: str 

3239 

3240 :rtype: bool 

3241 """ 

3242 assert isinstance(name, str) 

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

3244 

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

3246 # lobster-trace: LRM.Described_Name_Equality 

3247 # lobster-trace: LRM.Sufficiently_Distinct 

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

3249 

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

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

3252 

3253 :param mh: The message handler to use 

3254 :type mh: Message_Handler 

3255 

3256 :param name: The name to search for 

3257 :type name: str 

3258 

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

3260 is not an instance of the given class 

3261 :type required_subclass: type 

3262 

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

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

3265 :rtype: Entity 

3266 

3267 """ 

3268 assert isinstance(mh, Message_Handler) 

3269 assert isinstance(name, str) 

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

3271 

3272 simple_name = self.simplified_name(name) 

3273 

3274 ptr = self 

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

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

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

3278 rv = ptr.table[simple_name] 

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

3280 return None 

3281 

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

3283 not isinstance(rv, required_subclass): 

3284 mh.error(rv.location, 

3285 "%s %s is not a %s" % 

3286 (rv.__class__.__name__, 

3287 name, 

3288 required_subclass.__name__)) 

3289 return rv 

3290 else: 

3291 ptr = ptr.parent 

3292 

3293 return None 

3294 

3295 def lookup_direct(self, 

3296 mh, 

3297 name, 

3298 error_location, 

3299 required_subclass=None, 

3300 simplified=False): 

3301 # lobster-trace: LRM.Described_Name_Equality 

3302 # lobster-trace: LRM.Sufficiently_Distinct 

3303 # lobster-trace: LRM.Valid_Base_Names 

3304 # lobster-trace: LRM.Valid_Access_Prefixes 

3305 # lobster-trace: LRM.Valid_Function_Prefixes 

3306 """Retrieve an object from the table 

3307 

3308 For example:: 

3309 

3310 pkg = stab.lookup_direct(mh, 

3311 "potato", 

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

3313 Package) 

3314 

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

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

3317 Package, then the following error is issued:: 

3318 

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

3320 

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

3322 

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

3324 

3325 :param mh: The message handler to use 

3326 :type mh: Message_Handler 

3327 

3328 :param name: The name to search for 

3329 :type name: str 

3330 

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

3332 not found 

3333 :type error_location: Location 

3334 

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

3336 is not an instance of the given class 

3337 :type required_subclass: type 

3338 

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

3340 of the actual name 

3341 :type simplified: bool 

3342 

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

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

3345 :returns: the specified entity 

3346 :rtype: Entity 

3347 

3348 """ 

3349 assert isinstance(mh, Message_Handler) 

3350 assert isinstance(name, str) 

3351 assert isinstance(error_location, Location) 

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

3353 assert isinstance(simplified, bool) 

3354 

3355 simple_name = self.simplified_name(name) 

3356 ptr = self 

3357 options = [] 

3358 

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

3360 while ptr: 

3361 if simple_name in ptr.table: 

3362 rv = ptr.table[simple_name] 

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

3364 mh.error(error_location, 

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

3366 (name, 

3367 rv.name)) 

3368 

3369 if required_subclass is not None and \ 

3370 not isinstance(rv, required_subclass): 

3371 mh.error(error_location, 

3372 "%s %s is not a %s" % 

3373 (rv.__class__.__name__, 

3374 name, 

3375 required_subclass.__name__)) 

3376 return rv 

3377 else: 

3378 options += list(item.name 

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

3380 ptr = ptr.parent 

3381 

3382 matches = get_close_matches( 

3383 word = name, 

3384 possibilities = options, 

3385 n = 1) 

3386 

3387 if matches: 

3388 mh.error(error_location, 

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

3390 (name, 

3391 matches[0])) 

3392 else: 

3393 mh.error(error_location, 

3394 "unknown symbol %s" % name) 

3395 

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

3397 # lobster-trace: LRM.Described_Name_Equality 

3398 assert isinstance(mh, Message_Handler) 

3399 assert isinstance(referencing_token, Token) 

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

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

3402 

3403 return self.lookup_direct( 

3404 mh = mh, 

3405 name = referencing_token.value, 

3406 error_location = referencing_token.location, 

3407 required_subclass = required_subclass) 

3408 

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

3410 # lobster-exclude: Debugging feature 

3411 assert isinstance(indent, int) 

3412 assert indent >= 0 

3413 assert isinstance(message, str) 

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

3415 

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

3417 # lobster-exclude: Debugging feature 

3418 if omit_heading: 

3419 new_indent = indent 

3420 else: 

3421 self.write_indent(indent, "Symbol_Table") 

3422 new_indent = indent + 1 

3423 ptr = self 

3424 while ptr: 

3425 for name in ptr.table: 

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

3427 ptr = ptr.parent 

3428 

3429 @classmethod 

3430 def create_global_table(cls, mh): 

3431 # lobster-trace: LRM.Builtin_Types 

3432 # lobster-trace: LRM.Builtin_Functions 

3433 # lobster-trace: LRM.Builtin_Type_Conversion_Functions 

3434 # lobster-trace: LRM.Signature_Len 

3435 # lobster-trace: LRM.Signature_String_End_Functions 

3436 # lobster-trace: LRM.Signature_Matches 

3437 

3438 stab = Symbol_Table() 

3439 stab.register(mh, Builtin_Integer()) 

3440 stab.register(mh, Builtin_Decimal()) 

3441 stab.register(mh, Builtin_Boolean()) 

3442 stab.register(mh, Builtin_String()) 

3443 stab.register(mh, Builtin_Markup_String()) 

3444 stab.register(mh, 

3445 Builtin_Function("len", 1)) 

3446 stab.register(mh, 

3447 Builtin_Function("startswith", 2)) 

3448 stab.register(mh, 

3449 Builtin_Function("endswith", 2)) 

3450 stab.register(mh, 

3451 Builtin_Function("matches", 2)) 

3452 stab.register(mh, 

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

3454 

3455 return stab 

3456 

3457 

3458class Scope: 

3459 def __init__(self): 

3460 # lobster-exclude: Constructor only declares variables 

3461 self.scope = [] 

3462 

3463 def push(self, stab): 

3464 assert isinstance(stab, Symbol_Table) 

3465 self.scope.append(stab) 

3466 

3467 def pop(self): 

3468 self.scope.pop() 

3469 

3470 def contains(self, name): 

3471 assert isinstance(name, str) 

3472 

3473 for stab in reversed(self.scope): 

3474 if stab.contains(name): 

3475 return True 

3476 return False 

3477 

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

3479 assert len(self.scope) >= 1 

3480 assert isinstance(mh, Message_Handler) 

3481 assert isinstance(referencing_token, Token) 

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

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

3484 

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

3486 if stab.contains(referencing_token.value): 

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

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

3489 

3490 def size(self): 

3491 return len(self.scope)