Coverage for trlc/ast.py: 91%
1363 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-09-30 11:03 +0000
« prev ^ index » next coverage.py v7.16.2, created at 2026-09-30 11:03 +0000
1#!/usr/bin/env python3
2#
3# TRLC - Treat Requirements Like Code
4# Copyright (C) 2022-2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
5# Copyright (C) 2024-2025 Florian Schanda
6#
7# This file is part of the TRLC Python Reference Implementation.
8#
9# TRLC is free software: you can redistribute it and/or modify it
10# under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# TRLC is distributed in the hope that it will be useful, but WITHOUT
15# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
17# License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with TRLC. If not, see <https://www.gnu.org/licenses/>.
22from abc import ABCMeta, abstractmethod
23import re
25from copy import copy
26from difflib import get_close_matches
27from enum import Enum, auto
28from collections import OrderedDict
29from fractions import Fraction
31from trlc.errors import TRLC_Error, Location, Message_Handler
32from trlc.lexer import Token
33from trlc import math
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#
46##############################################################################
47# Valuations
48##############################################################################
51class Value:
52 # lobster-trace: LRM.Boolean_Values
53 # lobster-trace: LRM.Integer_Values
54 # lobster-trace: LRM.Decimal_Values
55 # lobster-trace: LRM.String_Values
56 # lobster-trace: LRM.Markup_String_Values
57 """Polymorphic value for evaluating expressions.
59 Any record references will be fully resolved.
61 :attribute location: source location this value comes from
62 :type: Location
64 :attribute value: the value or None (for null values)
65 :type: str, int, bool, fractions.Fraction, list[Value], \
66 Record_Reference, Enumeration_Literal_Spec
68 :attribute typ: type of the value (or None for null values)
69 :type: Type
70 """
72 def __init__(self, location, value, typ):
73 assert isinstance(location, Location)
74 assert value is None or isinstance(
75 value,
76 (
77 str,
78 int,
79 bool,
80 list, # for arrays
81 dict, # for tuples
82 Fraction,
83 Record_Reference,
84 Enumeration_Literal_Spec,
85 ),
86 )
87 assert typ is None or isinstance(typ, Type)
88 assert (typ is None) == (value is None)
90 self.location = location
91 self.value = value
92 self.typ = typ
94 def __eq__(self, other):
95 return self.typ == other.typ and self.value == other.value
97 def __repr__(self): # pragma: no cover
98 return "Value(%s)" % self.value
100 def resolve_references(self, mh):
101 assert isinstance(mh, Message_Handler)
103 if isinstance(self.value, Record_Reference):
104 self.value.resolve(mh)
107##############################################################################
108# AST Nodes
109##############################################################################
112class Node(metaclass=ABCMeta):
113 """Base class for all AST items.
115 :attribute location: source location
116 :type: Location
117 """
119 def __init__(self, location):
120 # lobster-exclude: Constructor only declares variables
121 assert isinstance(location, Location)
122 self.location = location
124 def set_ast_link(self, tok):
125 assert isinstance(tok, Token)
126 tok.ast_link = self
128 def write_indent(self, indent, message): # pragma: no cover
129 # lobster-exclude: Debugging feature
130 assert isinstance(indent, int)
131 assert indent >= 0
132 assert isinstance(message, str)
133 print(" " * (3 * indent) + message)
135 @abstractmethod
136 def dump(self, indent=0): # pragma: no cover
137 """Visualise the parse tree.
139 This can be called for any :class:`Node` or
140 :class:`Symbol_Table`, and can be very helpful for debugging
141 or understanding the parse tree. The dump method will produce
142 output like this::
144 Symbol_Table
145 Builtin_Boolean
146 Builtin_Integer
147 Builtin_Decimal
148 Builtin_String
149 Builtin_Markup_String
150 Package bar
151 Symbol_Table
152 Record_Type MyType
153 Composite_Component name
154 Optional: False
155 Type: String
156 Checks
157 Error 'description is too short'
158 Anchor: description
159 Binary Binary_Operator.COMP_GT Expression
160 Type: Boolean
161 Unary Unary_Operator.STRING_LENGTH Expression
162 Type: Integer
163 Name Reference to description
164 Integer Literal 10
165 Package instances
166 Symbol_Table
167 Record_Object SomeThing
168 Type: MyType
169 Field description: "Potato"
170 Builtin_Function endswith
171 Builtin_Function len
172 Builtin_Function matches
173 Builtin_Function startswith
174 Builtin_Function oneof
176 """
177 assert isinstance(indent, int) and indent >= 0
178 assert False, f"dump not implemented for {self.__class__.__name__}"
179 # lobster-exclude: Debugging feature
182class Check_Block(Node):
183 """Node representing check blocks
185 Semantically this has no meaning, but it's nice to have some kind
186 of similar representation to how it's in the file.
188 :attribute n_typ: composite type for which the checks apply
189 :type: Composite_Type
191 :attribute checks: list of checks
192 :type: list[Check]
194 """
196 def __init__(self, location, n_typ):
197 # lobster-trace: LRM.Check_Block
198 super().__init__(location)
199 assert isinstance(n_typ, Composite_Type)
200 self.n_typ = n_typ
201 self.checks = []
203 def add_check(self, n_check):
204 # lobster-trace: LRM.Check_Evaluation_Order
205 assert isinstance(n_check, Check)
206 self.checks.append(n_check)
208 def dump(self, indent=0): # pragma: no cover
209 # lobster-exclude: Debugging feature
210 self.write_indent(indent, "Check_Block")
211 self.write_indent(indent + 1, f"Type: {self.n_typ.name}")
212 for n_check in self.checks:
213 n_check.dump(indent + 1)
216class Compilation_Unit(Node):
217 """Special node to represent the concrete file structure
219 :attribute package: the main package this file declares or contributes to
220 :type: Package
222 :attribute imports: packages explicitly imported by this file
223 :type: set[Package]
225 :attribute wildcard_roots: roots of wildcard imports (``import foo.*``)
226 :type: set[Package]
228 :attribute raw_imports: unresolved imports as (name, location, \
229 is_wildcard, tokens)
230 :type: list[tuple[str, Location, bool, list[Token]]]
232 :attribute referenced_imports: imports actually used in this file
233 :type: set[Package]
235 :attribute items: list of
236 :type: list[Node]
238 """
240 def __init__(self, file_name):
241 # lobster-exclude: Constructor only declares variables
242 super().__init__(Location(file_name))
243 self.package = None
244 # Both default to empty sets (rather than None) so is_visible()
245 # behaves consistently even if called before resolve_imports()
246 # has run (e.g. only self-visibility is granted at that point).
247 self.imports = set()
248 self.wildcard_roots = set()
249 # list of (name : str, location : Location, is_wildcard : bool,
250 # tokens : list[Token])
251 self.raw_imports = []
252 self.referenced_imports = set()
253 self.items = []
255 def dump(self, indent=0): # pragma: no cover
256 # lobster-exclude: Debugging feature
257 self.write_indent(indent, f"Compilation_Unit ({self.location.file_name})")
258 for name, _location, is_wildcard, _tokens in self.raw_imports:
259 suffix = ".*" if is_wildcard else ""
260 self.write_indent(indent + 1, f"Import: {name}{suffix}")
261 for n_item in self.items:
262 n_item.dump(indent + 1)
264 def set_package(self, pkg):
265 # lobster-trace: LRM.Current_Package
266 assert isinstance(pkg, Package)
267 self.package = pkg
269 def add_import(self, mh, name, location, is_wildcard=False, tokens=None):
270 # lobster-trace: LRM.Import_Visibility
271 # lobster-trace: LRM.Self_Imports
272 # lobster-trace: LRM.Wildcard_Import
273 # lobster-trace: LRM.Wildcard_Self_Cover
274 assert isinstance(mh, Message_Handler)
275 assert isinstance(name, str)
276 assert isinstance(location, Location)
277 assert isinstance(is_wildcard, bool)
278 assert tokens is None or isinstance(tokens, list)
279 if tokens is None: 279 ↛ 280line 279 didn't jump to line 280 because the condition on line 279 was never true
280 tokens = []
282 # An explicit self-import is an error. A wildcard whose root
283 # covers the current package is permitted: the current package
284 # stays implicitly visible (Wildcard_Self_Cover).
285 if name == self.package.name and not is_wildcard:
286 mh.error(
287 location,
288 "package %s cannot import itself" % self.package.name,
289 explanation="a package always has access to its own "
290 "types and objects; remove this import statement",
291 )
293 # Skip duplicates (same name and same wildcard flavour)
294 for prev_name, _prev_location, prev_wildcard, _prev_tokens in self.raw_imports:
295 if prev_name == name and prev_wildcard == is_wildcard:
296 mh.warning(
297 location,
298 "duplicate import of package %s%s"
299 % (name, ".*" if is_wildcard else ""),
300 explanation="remove this redundant import "
301 "statement, the package is already imported",
302 )
303 return
305 self.raw_imports.append((name, location, is_wildcard, tokens))
307 def resolve_imports(self, mh, stab):
308 # lobster-trace: LRM.Import_Visibility
309 # lobster-trace: LRM.Wildcard_Import
310 assert isinstance(mh, Message_Handler)
311 assert isinstance(stab, Symbol_Table)
312 self.imports = set()
313 self.wildcard_roots = set()
314 for name, location, is_wildcard, tokens in self.raw_imports:
315 # We can ignore errors here, because that just means we
316 # generate more error later.
317 try:
318 a_import = stab.lookup_direct(mh, name, location, Package)
319 except TRLC_Error:
320 continue
321 # Link the import clause's tokens to the resolved package,
322 # so tooling (e.g. go-to-definition) can navigate to it.
323 for token in tokens:
324 a_import.set_ast_link(token)
325 if is_wildcard:
326 self.wildcard_roots.add(a_import)
327 else:
328 self.imports.add(a_import)
330 def covered_by_wildcard(self, pkg):
331 # lobster-trace: LRM.Wildcard_Import
332 # A package is covered by a wildcard root if it is the root
333 # itself or any descendant of it.
334 assert isinstance(pkg, Package)
335 for root in self.wildcard_roots:
336 if pkg == root or pkg.name.startswith(root.name + "."): 336 ↛ 335line 336 didn't jump to line 335 because the condition on line 336 was always true
337 return root
338 return None
340 def mark_import_used(self, pkg):
341 """Record that a package was actually referenced in this file.
343 Called during qualified-name resolution so the lint pass can
344 detect unused imports. If the package is visible through a
345 wildcard import, the wildcard root is marked used as well. A
346 reference to the current package itself never counts as using a
347 wildcard import, even if the wildcard's root covers the current
348 package (see LRM.Wildcard_Self_Cover): the current package is
349 always implicitly visible, not "imported" by the wildcard.
351 :param pkg: the package that was used
352 :type pkg: Package
353 """
354 assert isinstance(pkg, Package)
355 self.referenced_imports.add(pkg)
356 if pkg is self.package:
357 return
358 root = self.covered_by_wildcard(pkg)
359 if root is not None:
360 self.referenced_imports.add(root)
362 def is_visible(self, n_pkg):
363 # lobster-trace: LRM.Import_Visibility
364 # lobster-trace: LRM.Wildcard_Import
365 # lobster-trace: LRM.Nested_Visibility
366 assert isinstance(n_pkg, Package)
367 if n_pkg == self.package or n_pkg in self.imports:
368 return True
369 return self.covered_by_wildcard(n_pkg) is not None
371 def add_item(self, node):
372 # lobster-trace: LRM.RSL_File
373 # lobster-trace: LRM.TRLC_File
374 assert isinstance(node, (Concrete_Type, Check_Block, Record_Object)), (
375 "trying to add %s to a compilation unit" % node.__class__.__name__
376 )
377 self.items.append(node)
380class Check(Node):
381 """User defined check
383 This represent a single user-defined check inside a check block::
385 checks T {
386 a /= null implies a > 5, warning "potato", a
387 ^^^^^^^^^^^^^^^^^^^^^^^1 ^2 ^3 ^4
389 :attribute n_type: The tuple/record type this check applies to
390 :type: Composite_Type
392 :attribute n_expr: The boolean expression for the check (see 1)
393 :type: Expression
395 :attribute n_anchor: The (optional) record component where the message \
396 should be issued (or None) (see 4)
397 :type: Composite_Component
399 :attribute severity: warning, error, or fatal (see 2; also if this is \
400 not specified the default is 'error')
401 :type: str
403 :attribute message: the user-supplied message (see 3)
404 :type: str
405 """
407 def __init__(self, n_type, n_expr, n_anchor, severity, t_message, extrainfo):
408 # lobster-trace: LRM.Check_Block
409 assert isinstance(n_type, Composite_Type)
410 assert isinstance(n_expr, Expression)
411 assert isinstance(n_anchor, Composite_Component) or n_anchor is None
412 assert severity in ("warning", "error", "fatal")
413 assert isinstance(t_message, Token)
414 assert t_message.kind == "STRING"
415 assert isinstance(extrainfo, str) or extrainfo is None
416 super().__init__(t_message.location)
418 self.n_type = n_type
419 self.n_expr = n_expr
420 self.n_anchor = n_anchor
421 self.severity = severity
422 # lobster-trace: LRM.No_Newlines_In_Message
423 # This is the error recovery strategy if we find newlines in
424 # the short error messages: we just remove them. The error
425 # raised is non-fatal.
426 self.message = t_message.value.replace("\n", " ")
427 self.extrainfo = extrainfo
428 self._uses_field_access = None
430 @property
431 def uses_field_access(self):
432 """Cached test: does this check's expression follow a record/union
433 reference?
435 Returns True if any sub-expression of the check expression is a
436 :class:`Field_Access_Expression` whose prefix has a
437 :class:`Record_Type` or :class:`Union_Type` type. Used by
438 the VCG to split checks into Phase A ("at declaration") and
439 Phase B ("after references").
441 :return: whether this check dereferences a record/union reference
442 :rtype: bool
444 """
445 if self._uses_field_access is None:
446 self._uses_field_access = self.n_expr.uses_field_access()
447 return self._uses_field_access
449 def dump(self, indent=0): # pragma: no cover
450 # lobster-exclude: Debugging feature
451 if self.severity == "warning":
452 self.write_indent(indent, f"Warning '{self.message}'")
453 elif self.severity == "error":
454 self.write_indent(indent, f"Error '{self.message}'")
455 else:
456 self.write_indent(indent, f"Fatal error '{self.message}'")
457 if self.n_anchor:
458 self.write_indent(indent + 1, f"Anchor: {self.n_anchor.name}")
459 self.n_expr.dump(indent + 1)
461 def get_real_location(self, composite_object):
462 # lobster-exclude: LRM.Anchoring
463 assert isinstance(composite_object, (Record_Object, Tuple_Aggregate))
464 if isinstance(composite_object, Record_Object):
465 fields = composite_object.field
466 else:
467 fields = composite_object.value
469 if self.n_anchor is None or fields[self.n_anchor.name] is None:
470 return composite_object.location
471 else:
472 return fields[self.n_anchor.name].location
474 def perform(self, mh, composite_object, gstab):
475 # lobster-trace: LRM.Check_Messages
476 # lobster-trace: LRM.Check_Severity
477 assert isinstance(mh, Message_Handler)
478 assert isinstance(composite_object, (Record_Object, Tuple_Aggregate))
479 assert isinstance(gstab, Symbol_Table)
481 if isinstance(composite_object, Record_Object):
482 result = self.n_expr.evaluate(mh, copy(composite_object.field), gstab)
483 else:
484 result = self.n_expr.evaluate(mh, copy(composite_object.value), gstab)
485 if result.value is None:
486 loc = self.get_real_location(composite_object)
487 mh.error(
488 loc,
489 "check %s (%s) evaluates to null"
490 % (self.n_expr.to_string(), mh.cross_file_reference(self.location)),
491 )
493 assert isinstance(result.value, bool)
495 if not result.value:
496 loc = self.get_real_location(composite_object)
497 if self.severity == "warning":
498 mh.warning(
499 location=loc,
500 message=self.message,
501 explanation=self.extrainfo,
502 user=True,
503 )
504 else:
505 mh.error(
506 location=loc,
507 message=self.message,
508 explanation=self.extrainfo,
509 fatal=self.severity == "fatal",
510 user=True,
511 )
512 return False
514 return True
517##############################################################################
518# AST Nodes (Expressions)
519##############################################################################
522class Unary_Operator(Enum):
523 # lobster-exclude: Utility enumeration for unary operators
524 MINUS = auto()
525 PLUS = auto()
526 LOGICAL_NOT = auto()
527 ABSOLUTE_VALUE = auto()
529 STRING_LENGTH = auto()
530 ARRAY_LENGTH = auto()
532 CONVERSION_TO_INT = auto()
533 CONVERSION_TO_DECIMAL = auto()
536class Binary_Operator(Enum):
537 # lobster-exclude: Utility enumeration for binary operators
538 LOGICAL_AND = auto() # Short-circuit
539 LOGICAL_OR = auto() # Short-circuit
540 LOGICAL_XOR = auto()
541 LOGICAL_IMPLIES = auto() # Short-circuit
543 COMP_EQ = auto()
544 COMP_NEQ = auto()
545 COMP_LT = auto()
546 COMP_LEQ = auto()
547 COMP_GT = auto()
548 COMP_GEQ = auto()
550 STRING_CONTAINS = auto()
551 STRING_STARTSWITH = auto()
552 STRING_ENDSWITH = auto()
553 STRING_REGEX = auto()
555 ARRAY_CONTAINS = auto()
557 PLUS = auto()
558 MINUS = auto()
559 TIMES = auto()
560 DIVIDE = auto()
561 REMAINDER = auto()
563 POWER = auto()
565 INDEX = auto()
568class Expression(Node, metaclass=ABCMeta):
569 """Abstract base class for all expressions.
571 :attribute typ: The type of this expression (or None for null values)
572 :type: Type
573 """
575 def __init__(self, location, typ):
576 # lobster-exclude: Constructor only declares variables
577 super().__init__(location)
578 assert typ is None or isinstance(typ, Type)
579 self.typ = typ
581 def evaluate(self, mh, context, gstab): # pragma: no cover
582 """Evaluate the expression in the given context
584 The context can be None, in which case the expression is
585 evaluated in a static context. Otherwise it must be a
586 dictionary that maps names (such as record fields or
587 quantified variables) to expressions.
589 The global symbol table must be None (for static context
590 evaluations), otherwise it must contain the global symbol
591 table to resolve record references.
593 :param mh: the message handler to use
594 :type mh: Message_Handler
595 :param context: name mapping or None (for a static context)
596 :type context: dict[str, Expression]
597 :raise TRLC_Error: if the expression cannot be evaluated
598 :return: result of the evaluation
599 :rtype: Value
601 """
602 assert isinstance(mh, Message_Handler)
603 assert context is None or isinstance(context, dict)
604 assert gstab is None or isinstance(gstab, Symbol_Table)
605 assert False, "evaluate not implemented for %s" % self.__class__.__name__
607 @abstractmethod
608 def to_string(self): # pragma: no cover
609 assert False, "to_string not implemented for %s" % self.__class__.__name__
611 def ensure_type(self, mh, typ):
612 # lobster-trace: LRM.Restricted_Null
613 # lobster-trace: LRM.Null_Is_Invalid
615 assert isinstance(typ, (type, Type))
616 if self.typ is None:
617 mh.error(self.location, "null is not permitted here")
618 elif isinstance(typ, type) and not isinstance(self.typ, typ):
619 mh.error(
620 self.location,
621 "expected expression of type %s, got %s instead"
622 % (typ.__name__, self.typ.__class__.__name__),
623 )
624 elif isinstance(typ, Type) and self.typ != typ:
625 mh.error(
626 self.location,
627 "expected expression of type %s, got %s instead"
628 % (typ.name, self.typ.name),
629 )
631 def resolve_references(self, mh):
632 assert isinstance(mh, Message_Handler)
634 @abstractmethod
635 def can_be_null(self):
636 """Test if the expression could return null
638 Checks the expression if it could generate a null value
639 *without* raising an error. For example `x` could generate a
640 null value if `x` is a record component that is
641 optional. However `x + 1` could not, since an error would
642 occur earlier.
644 :return: possibility of encountering null
645 :rtype: bool
647 """
648 assert False, "can_be_null not implemented for %s" % self.__class__.__name__
650 def uses_field_access(self):
651 """Test if this expression contains a field access on a record or
652 union reference.
654 Returns True if any sub-expression is a
655 :class:`Field_Access_Expression` whose prefix has a
656 :class:`Record_Type` or :class:`Union_Type` type. This is
657 used by the VCG to split checks into "at declaration"
658 (Phase A) and "after references" (Phase B).
660 :return: whether this expression follows a record/union reference
661 :rtype: bool
663 """
664 return False
667class Implicit_Null(Expression):
668 """Synthesised null values
670 When a record object or tuple aggregate is declared and an
671 optional component or field is not specified, we synthesise an
672 implicit null expression for this.
674 For example given this TRLC type::
676 type T {
677 x optional Integer
678 }
680 And this declaration::
682 T Potato {}
684 Then the field mapping for Potato will be::
686 {x: Implicit_Null}
688 Each field will get its own implicit null. Further note that this
689 implicit null is distinct from the explicit :class:`Null_Literal`
690 that can appear in check expressions.
692 """
694 def __init__(self, composite_object, composite_component):
695 # lobster-trace: LRM.Unspecified_Optional_Components
696 assert isinstance(composite_object, (Record_Object, Tuple_Aggregate))
697 assert isinstance(composite_component, Composite_Component)
698 super().__init__(composite_object.location, None)
700 def to_string(self):
701 return "null"
703 def evaluate(self, mh, context, gstab):
704 # lobster-trace: LRM.Unspecified_Optional_Components
705 assert isinstance(mh, Message_Handler)
706 assert context is None or isinstance(context, dict)
707 assert gstab is None or isinstance(gstab, Symbol_Table)
708 return Value(self.location, None, None)
710 def to_python_object(self):
711 return None
713 def dump(self, indent=0): # pragma: no cover
714 # lobster-exclude: Debugging feature
715 self.write_indent(indent, "Implicit_Null")
717 def can_be_null(self):
718 return True
721class Literal(Expression, metaclass=ABCMeta):
722 """Abstract base for all Literals
724 Does not offer any additional features, but it's a nice way to
725 group together all literal types. This is useful if you want to
726 check if you are dealing with a literal::
728 isinstance(my_expression, Literal)
730 """
732 @abstractmethod
733 def to_python_object(self):
734 assert False
737class Null_Literal(Literal):
738 # lobster-trace: LRM.Primary
739 """The null literal
741 This can appear in check expressions::
743 a /= null implies a > 5
744 ^^^^
746 Please note that this is distinct from the :class:`Implicit_Null`
747 values that appear in record objects.
749 """
751 def __init__(self, token):
752 assert isinstance(token, Token)
753 assert token.kind == "KEYWORD"
754 assert token.value == "null"
755 super().__init__(token.location, None)
757 def dump(self, indent=0): # pragma: no cover
758 self.write_indent(indent, "Null Literal")
760 def to_string(self):
761 return "null"
763 def evaluate(self, mh, context, gstab):
764 assert isinstance(mh, Message_Handler)
765 assert context is None or isinstance(context, dict)
766 assert gstab is None or isinstance(gstab, Symbol_Table)
767 return Value(self.location, None, None)
769 def to_python_object(self):
770 return None
772 def can_be_null(self):
773 return True
776class Integer_Literal(Literal):
777 # lobster-trace: LRM.Integer_Values
778 # lobster-trace: LRM.Primary
779 """Integer literals
781 Note that these are always positive. A negative integer is
782 actually a unary negation expression, operating on a positive
783 integer literal::
785 x == -5
787 This would create the following tree::
789 OP_EQUALITY
790 NAME_REFERENCE x
791 UNARY_EXPRESSION -
792 INTEGER_LITERAL 5
794 :attribute value: the non-negative integer value
795 :type: int
796 """
798 def __init__(self, token, typ):
799 assert isinstance(token, Token)
800 assert token.kind == "INTEGER"
801 assert isinstance(typ, Builtin_Integer)
802 super().__init__(token.location, typ)
804 self.value = token.value
806 def dump(self, indent=0): # pragma: no cover
807 self.write_indent(indent, f"Integer Literal {self.value}")
809 def to_string(self):
810 return str(self.value)
812 def evaluate(self, mh, context, gstab):
813 assert isinstance(mh, Message_Handler)
814 assert context is None or isinstance(context, dict)
815 assert gstab is None or isinstance(gstab, Symbol_Table)
816 return Value(self.location, self.value, self.typ)
818 def to_python_object(self):
819 return self.value
821 def can_be_null(self):
822 return False
825class Decimal_Literal(Literal):
826 # lobster-trace: LRM.Decimal_Values
827 # lobster-trace: LRM.Primary
828 """Decimal literals
830 Note that these are always positive. A negative decimal is
831 actually a unary negation expression, operating on a positive
832 decimal literal::
834 x == -5.0
836 This would create the following tree::
838 OP_EQUALITY
839 NAME_REFERENCE x
840 UNARY_EXPRESSION -
841 DECIMAL_LITERAL 5.0
843 :attribute value: the non-negative decimal value
844 :type: fractions.Fraction
845 """
847 def __init__(self, token, typ):
848 assert isinstance(token, Token)
849 assert token.kind == "DECIMAL"
850 assert isinstance(typ, Builtin_Decimal)
851 super().__init__(token.location, typ)
853 self.value = token.value
855 def dump(self, indent=0): # pragma: no cover
856 self.write_indent(indent, f"Decimal Literal {self.value}")
858 def to_string(self):
859 return str(self.value)
861 def evaluate(self, mh, context, gstab):
862 assert isinstance(mh, Message_Handler)
863 assert context is None or isinstance(context, dict)
864 assert gstab is None or isinstance(gstab, Symbol_Table)
865 return Value(self.location, self.value, self.typ)
867 def to_python_object(self):
868 return float(self.value)
870 def can_be_null(self):
871 return False
874class String_Literal(Literal):
875 # lobster-trace: LRM.String_Values
876 # lobster-trace: LRM.Markup_String_Values
877 # lobster-trace: LRM.Primary
878 """String literals
880 Note the value of the string does not include the quotation marks,
881 and any escape sequences are fully resolved. For example::
883 "foo\\"bar"
885 Will have a value of ``foo"bar``.
887 :attribute value: string content
888 :type: str
890 :attribute references: resolved references of a markup string
891 :type: list[Record_Reference]
893 """
895 def __init__(self, token, typ):
896 assert isinstance(token, Token)
897 assert token.kind == "STRING"
898 assert isinstance(typ, Builtin_String)
899 super().__init__(token.location, typ)
901 self.value = token.value
902 self.has_references = isinstance(typ, Builtin_Markup_String)
903 self.references = []
905 def dump(self, indent=0): # pragma: no cover
906 self.write_indent(indent, f"String Literal {repr(self.value)}")
907 if self.has_references:
908 self.write_indent(indent + 1, "Markup References")
909 for ref in self.references:
910 ref.dump(indent + 2)
912 def to_string(self):
913 return self.value
915 def evaluate(self, mh, context, gstab):
916 assert isinstance(mh, Message_Handler)
917 assert context is None or isinstance(context, dict)
918 assert gstab is None or isinstance(gstab, Symbol_Table)
919 return Value(self.location, self.value, self.typ)
921 def to_python_object(self):
922 return self.value
924 def resolve_references(self, mh):
925 assert isinstance(mh, Message_Handler)
926 for ref in self.references:
927 ref.resolve_references(mh)
929 def can_be_null(self):
930 return False
933class Boolean_Literal(Literal):
934 # lobster-trace: LRM.Boolean_Values
935 # lobster-trace: LRM.Primary
936 """Boolean values
938 :attribute value: the boolean value
939 :type: bool
940 """
942 def __init__(self, token, typ):
943 assert isinstance(token, Token)
944 assert token.kind == "KEYWORD"
945 assert token.value in ("false", "true")
946 assert isinstance(typ, Builtin_Boolean)
947 super().__init__(token.location, typ)
949 self.value = token.value == "true"
951 def dump(self, indent=0): # pragma: no cover
952 self.write_indent(indent, f"Boolean Literal {self.value}")
954 def to_string(self):
955 return str(self.value)
957 def evaluate(self, mh, context, gstab):
958 assert isinstance(mh, Message_Handler)
959 assert context is None or isinstance(context, dict)
960 assert gstab is None or isinstance(gstab, Symbol_Table)
961 return Value(self.location, self.value, self.typ)
963 def to_python_object(self):
964 return self.value
966 def can_be_null(self):
967 return False
970class Enumeration_Literal(Literal):
971 """Enumeration values
973 Note that this is distinct from
974 :class:`Enumeration_Literal_Spec`. An enumeration literal is a
975 specific mention of an enumeration member in an expression::
977 foo != my_enum.POTATO
978 ^^^^^^^^^^^^^^
980 To get to the string value of the enumeration literal
981 (i.e. ``POTATO`` here) you can get the name of the literal spec
982 itself: ``enum_lit.value.name``; and to get the name of the
983 enumeration (i.e. ``my_enum`` here) you can use
984 ``enum_lit.value.n_typ.name``.
986 :attribute value: enumeration value
987 :type: Enumeration_Literal_Spec
989 """
991 def __init__(self, location, literal):
992 # lobster-exclude: Constructor only declares variables
993 assert isinstance(literal, Enumeration_Literal_Spec)
994 super().__init__(location, literal.n_typ)
996 self.value = literal
998 def dump(self, indent=0): # pragma: no cover
999 # lobster-exclude: Debugging feature
1000 self.write_indent(
1001 indent, f"Enumeration Literal {self.typ.name}.{self.value.name}"
1002 )
1004 def to_string(self):
1005 return self.typ.name + "." + self.value.name
1007 def evaluate(self, mh, context, gstab):
1008 assert isinstance(mh, Message_Handler)
1009 assert context is None or isinstance(context, dict)
1010 assert gstab is None or isinstance(gstab, Symbol_Table)
1011 return Value(self.location, self.value, self.typ)
1013 def to_python_object(self):
1014 return self.value.name
1016 def can_be_null(self):
1017 return False
1020class Array_Aggregate(Expression):
1021 """Instances of array types
1023 This is created when assigning to array components::
1025 potatoes = ["picasso", "yukon gold", "sweet"]
1026 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1028 The type of expression that can be found in an array is somewhat
1029 limited:
1031 * :class:`Literal`
1032 * :class:`Array_Aggregate`
1033 * :class:`Record_Reference`
1035 :attribute value: contents of the array
1036 :type: list[Expression]
1038 """
1040 def __init__(self, location, typ):
1041 # lobster-trace: LRM.Record_Object_Declaration
1043 super().__init__(location, typ)
1044 self.value = []
1046 def dump(self, indent=0): # pragma: no cover
1047 # lobster-exclude: Debugging feature
1048 self.write_indent(indent, "Array_Aggregate")
1049 for n_value in self.value:
1050 n_value.dump(indent + 1)
1052 def append(self, value):
1053 assert isinstance(
1054 value,
1055 (
1056 Literal,
1057 Unary_Expression,
1058 Array_Aggregate,
1059 Tuple_Aggregate,
1060 Record_Reference,
1061 ),
1062 )
1063 self.value.append(value)
1065 def to_string(self):
1066 return "[" + ", ".join(x.to_string() for x in self.value) + "]"
1068 def evaluate(self, mh, context, gstab):
1069 assert isinstance(mh, Message_Handler)
1070 assert context is None or isinstance(context, dict)
1071 assert gstab is None or isinstance(gstab, Symbol_Table)
1072 return Value(
1073 self.location,
1074 list(element.evaluate(mh, context, gstab) for element in self.value),
1075 self.typ,
1076 )
1078 def resolve_references(self, mh):
1079 assert isinstance(mh, Message_Handler)
1081 for val in self.value:
1082 val.resolve_references(mh)
1084 def to_python_object(self):
1085 return [x.to_python_object() for x in self.value]
1087 def can_be_null(self):
1088 return False
1090 def uses_field_access(self):
1091 return any(expr.uses_field_access() for expr in self.value)
1094class Tuple_Aggregate(Expression):
1095 """Instances of a tuple
1097 This is created when assigning to a tuple components. There are
1098 two forms, the ordinary form::
1100 coordinate = (12.3, 40.0)
1101 ^^^^^^^^^^^^
1103 And the separator form::
1105 item = 12345@42
1106 ^^^^^^^^
1108 In terms of AST there is no difference, as the separator is only
1109 syntactic sugar.
1111 :attribute value: contents of the tuple
1112 :type: dict[str, Expression]
1114 """
1116 def __init__(self, location, typ):
1117 # lobster-trace: LRM.Unspecified_Optional_Components
1118 # lobster-trace: LRM.Record_Object_Declaration
1120 super().__init__(location, typ)
1121 self.value = {
1122 n_field.name: Implicit_Null(self, n_field)
1123 for n_field in self.typ.components.values()
1124 }
1126 def assign(self, field, value):
1127 assert isinstance(field, str)
1128 assert isinstance(
1129 value, (Literal, Unary_Expression, Tuple_Aggregate, Record_Reference)
1130 ), "value is %s" % value.__class__.__name__
1131 assert field in self.typ.components
1133 self.value[field] = value
1135 def dump(self, indent=0): # pragma: no cover
1136 # lobster-exclude: Debugging feature
1137 self.write_indent(indent, "Tuple_Aggregate")
1138 self.write_indent(indent + 1, f"Type: {self.typ.name}")
1139 for n_item in self.typ.iter_sequence():
1140 if isinstance(n_item, Composite_Component):
1141 self.value[n_item.name].dump(indent + 1)
1143 def to_string(self):
1144 first = True
1145 if self.typ.has_separators():
1146 rv = ""
1147 else:
1148 rv = "("
1149 for n_item in self.typ.iter_sequence():
1150 if isinstance(n_item, Separator):
1151 rv += " %s " % n_item.token.value
1152 elif first:
1153 first = False
1154 else:
1155 rv += ", "
1157 if isinstance(n_item, Composite_Component):
1158 rv += self.value[n_item.name].to_string()
1159 if self.typ.has_separators():
1160 rv = ""
1161 else:
1162 rv = ")"
1163 return rv
1165 def evaluate(self, mh, context, gstab):
1166 assert isinstance(mh, Message_Handler)
1167 assert context is None or isinstance(context, dict)
1168 assert gstab is None or isinstance(gstab, Symbol_Table)
1169 return Value(
1170 self.location,
1171 {
1172 name: element.evaluate(mh, context, gstab)
1173 for name, element in self.value.items()
1174 },
1175 self.typ,
1176 )
1178 def resolve_references(self, mh):
1179 assert isinstance(mh, Message_Handler)
1181 for val in self.value.values():
1182 val.resolve_references(mh)
1184 def to_python_object(self):
1185 return {name: value.to_python_object() for name, value in self.value.items()}
1187 def can_be_null(self):
1188 return False
1190 def uses_field_access(self):
1191 return any(expr.uses_field_access() for expr in self.value.values())
1194class Record_Reference(Expression):
1195 """Reference to another record object
1197 This can appear in record object declarations::
1199 Requirement Kitten {
1200 depends_on = Other_Package.Cat
1201 ^1 ^2
1202 }
1204 Note that this is distinct from :class:`Record_Object`. It is just
1205 the name; to get to the object referred to by this you can consult
1206 the target attribute.
1208 The reason we have this indirection is that not all names can be
1209 immediately resolved on parsing in the TRLC language.
1211 Note that while the containing package (see 1) is optional in the
1212 source language, the containing package will always be filled in
1213 in this AST node.
1215 :attribute name: The name of the record (see 2)
1216 :type: str
1218 :attribute target: The concrete record object referred to by (2)
1219 :type: Record_Object
1221 :attribute package: The package (see 1) supposed to contain (2)
1222 :type: Package
1224 """
1226 def __init__(self, location, name, typ, package):
1227 # lobster-exclude: Constructor only declares variables
1228 assert isinstance(location, Location)
1229 assert isinstance(name, str)
1230 assert isinstance(typ, (Record_Type, Union_Type)) or typ is None
1231 assert isinstance(package, Package)
1232 super().__init__(location, typ)
1234 self.name = name
1235 self.target = None
1236 self.package = package
1238 def dump(self, indent=0): # pragma: no cover
1239 # lobster-exclude: Debugging feature
1240 self.write_indent(indent, f"Record Reference {self.name}")
1241 self.write_indent(indent + 1, f"Resolved: {self.target is not None}")
1243 def to_string(self):
1244 return self.name
1246 def evaluate(self, mh, context, gstab):
1247 assert isinstance(mh, Message_Handler)
1248 assert context is None or isinstance(context, dict)
1249 assert gstab is None or isinstance(gstab, Symbol_Table)
1250 return Value(self.location, copy(self.target.field), self.typ)
1252 def resolve_references(self, mh):
1253 # lobster-trace: LRM.References_To_Extensions
1254 # lobster-trace: LRM.Union_Type_Minimum_Members
1255 assert isinstance(mh, Message_Handler)
1257 self.target = self.package.symbols.lookup_direct(
1258 mh=mh,
1259 name=self.name,
1260 error_location=self.location,
1261 required_subclass=Record_Object,
1262 )
1263 if self.typ is None:
1264 self.typ = self.target.n_typ
1265 elif isinstance(self.typ, Union_Type):
1266 if not self.typ.is_compatible(self.target.n_typ):
1267 mh.error(
1268 self.location,
1269 "expected reference of type %s,"
1270 " but %s is of type %s"
1271 % (self.typ.name, self.target.name, self.target.n_typ.name),
1272 )
1273 elif not self.target.n_typ.is_subclass_of(self.typ):
1274 mh.error(
1275 self.location,
1276 "expected reference of type %s, but %s is of type %s"
1277 % (self.typ.name, self.target.name, self.target.n_typ.name),
1278 )
1280 def to_python_object(self):
1281 return self.target.fully_qualified_name()
1283 def can_be_null(self):
1284 return False
1287class Name_Reference(Expression):
1288 # lobster-trace: LRM.Qualified_Name
1289 # lobster-trace: LRM.Static_Regular_Expression
1291 """Reference to a name
1293 Name reference to either a :class:`Composite_Component` or a
1294 :class:`Quantified_Variable`. The actual value of course depends
1295 on the context. See :py:meth:`Expression.evaluate()`.
1297 For example::
1299 (forall x in potato => x > 1)
1300 ^1 ^2
1302 Both indicated parts are a :class:`Name_Reference`, the first one
1303 refers to a :class:`Composite_Component`, and the second refers to a
1304 :class:`Quantified_Variable`.
1306 :attribute entity: the entity named here
1307 :type: Composite_Component, Quantified_Variable
1308 """
1310 def __init__(self, location, entity):
1311 assert isinstance(entity, (Composite_Component, Quantified_Variable))
1312 super().__init__(location, entity.n_typ)
1313 self.entity = entity
1315 def dump(self, indent=0): # pragma: no cover
1316 self.write_indent(indent, f"Name Reference to {self.entity.name}")
1318 def to_string(self):
1319 return self.entity.name
1321 def evaluate(self, mh, context, gstab):
1322 assert isinstance(mh, Message_Handler)
1323 assert context is None or isinstance(context, dict)
1324 assert gstab is None or isinstance(gstab, Symbol_Table)
1326 if context is None:
1327 mh.error(self.location, "cannot be used in a static context")
1329 assert self.entity.name in context
1330 return context[self.entity.name].evaluate(mh, context, gstab)
1332 def can_be_null(self):
1333 # The only way we could generate null here (without raising
1334 # error earlier) is when we refer to a component that is
1335 # optional.
1336 if isinstance(self.entity, Composite_Component):
1337 return self.entity.optional
1338 else:
1339 return False
1342class Unary_Expression(Expression):
1343 """Expression with only one operand
1345 This captures the following operations:
1347 * Unary_Operator.PLUS (e.g. ``+5``)
1348 * Unary_Operator.MINUS (e.g. ``-5``)
1349 * Unary_Operator.ABSOLUTE_VALUE (e.g. ``abs 42``)
1350 * Unary_Operator.LOGICAL_NOT (e.g. ``not True``)
1351 * Unary_Operator.STRING_LENGTH (e.g. ``len("foobar")``)
1352 * Unary_Operator.ARRAY_LENGTH (e.g. ``len(component_name)``)
1353 * Unary_Operator.CONVERSION_TO_INT (e.g. ``Integer(5.3)``)
1354 * Unary_Operator.CONVERSION_TO_DECIMAL (e.g. ``Decimal(5)``)
1356 Note that several builtin functions are mapped to unary operators.
1358 :attribute operator: the operation
1359 :type: Unary_Operator
1361 :attribute n_operand: the expression we operate on
1362 :type: Expression
1364 """
1366 def __init__(self, mh, location, typ, operator, n_operand):
1367 # lobster-trace: LRM.Simple_Expression
1368 # lobster-trace: LRM.Relation
1369 # lobster-trace: LRM.Factor
1370 # lobster-trace: LRM.Signature_Len
1371 # lobster-trace: LRM.Signature_Type_Conversion
1373 super().__init__(location, typ)
1374 assert isinstance(mh, Message_Handler)
1375 assert isinstance(operator, Unary_Operator)
1376 assert isinstance(n_operand, Expression)
1377 self.operator = operator
1378 self.n_operand = n_operand
1380 if operator in (
1381 Unary_Operator.MINUS,
1382 Unary_Operator.PLUS,
1383 Unary_Operator.ABSOLUTE_VALUE,
1384 ):
1385 self.n_operand.ensure_type(mh, Builtin_Numeric_Type)
1386 elif operator == Unary_Operator.LOGICAL_NOT:
1387 self.n_operand.ensure_type(mh, Builtin_Boolean)
1388 elif operator == Unary_Operator.STRING_LENGTH:
1389 self.n_operand.ensure_type(mh, Builtin_String)
1390 elif operator == Unary_Operator.ARRAY_LENGTH:
1391 self.n_operand.ensure_type(mh, Array_Type)
1392 elif operator == Unary_Operator.CONVERSION_TO_INT:
1393 self.n_operand.ensure_type(mh, Builtin_Numeric_Type)
1394 elif operator == Unary_Operator.CONVERSION_TO_DECIMAL:
1395 self.n_operand.ensure_type(mh, Builtin_Numeric_Type)
1396 else:
1397 mh.ice_loc(self.location, "unexpected unary operation %s" % operator)
1399 def to_string(self):
1400 prefix_operators = {
1401 Unary_Operator.MINUS: "-",
1402 Unary_Operator.PLUS: "+",
1403 Unary_Operator.ABSOLUTE_VALUE: "abs ",
1404 Unary_Operator.LOGICAL_NOT: "not ",
1405 }
1406 function_calls = {
1407 Unary_Operator.STRING_LENGTH: "len",
1408 Unary_Operator.ARRAY_LENGTH: "len",
1409 Unary_Operator.CONVERSION_TO_INT: "Integer",
1410 Unary_Operator.CONVERSION_TO_DECIMAL: "Decimal",
1411 }
1413 if self.operator in prefix_operators:
1414 return prefix_operators[self.operator] + self.n_operand.to_string()
1416 elif self.operator in function_calls:
1417 return "%s(%s)" % (
1418 function_calls[self.operator],
1419 self.n_operand.to_string(),
1420 )
1422 else:
1423 assert False
1425 def dump(self, indent=0): # pragma: no cover
1426 # lobster-exclude: Debugging feature
1427 self.write_indent(indent, f"Unary {self.operator} Expression")
1428 self.write_indent(indent + 1, f"Type: {self.typ.name}")
1429 self.n_operand.dump(indent + 1)
1431 def evaluate(self, mh, context, gstab):
1432 # lobster-trace: LRM.Null_Is_Invalid
1433 # lobster-trace: LRM.Signature_Len
1434 # lobster-trace: LRM.Signature_Type_Conversion
1435 # lobster-trace: LRM.Len_Semantics
1436 # lobster-trace: LRM.Integer_Conversion_Semantics
1437 # lobster-trace: LRM.Decimal_Conversion_Semantics
1439 assert isinstance(mh, Message_Handler)
1440 assert context is None or isinstance(context, dict)
1441 assert gstab is None or isinstance(gstab, Symbol_Table)
1443 v_operand = self.n_operand.evaluate(mh, context, gstab)
1444 if v_operand.value is None: 1444 ↛ 1445line 1444 didn't jump to line 1445 because the condition on line 1444 was never true
1445 mh.error(
1446 v_operand.location,
1447 "input to unary expression %s (%s) must not be null"
1448 % (self.to_string(), mh.cross_file_reference(self.location)),
1449 )
1451 if self.operator == Unary_Operator.MINUS:
1452 return Value(location=self.location, value=-v_operand.value, typ=self.typ)
1453 elif self.operator == Unary_Operator.PLUS:
1454 return Value(location=self.location, value=+v_operand.value, typ=self.typ)
1455 elif self.operator == Unary_Operator.LOGICAL_NOT:
1456 return Value(
1457 location=self.location, value=not v_operand.value, typ=self.typ
1458 )
1459 elif self.operator == Unary_Operator.ABSOLUTE_VALUE:
1460 return Value(
1461 location=self.location, value=abs(v_operand.value), typ=self.typ
1462 )
1463 elif self.operator in (
1464 Unary_Operator.STRING_LENGTH,
1465 Unary_Operator.ARRAY_LENGTH,
1466 ):
1467 return Value(
1468 location=self.location, value=len(v_operand.value), typ=self.typ
1469 )
1470 elif self.operator == Unary_Operator.CONVERSION_TO_INT:
1471 if isinstance(v_operand.value, Fraction): 1471 ↛ 1478line 1471 didn't jump to line 1478 because the condition on line 1471 was always true
1472 return Value(
1473 location=self.location,
1474 value=math.round_nearest_away(v_operand.value),
1475 typ=self.typ,
1476 )
1477 else:
1478 return Value(
1479 location=self.location, value=v_operand.value, typ=self.typ
1480 )
1481 elif self.operator == Unary_Operator.CONVERSION_TO_DECIMAL:
1482 return Value(
1483 location=self.location, value=Fraction(v_operand.value), typ=self.typ
1484 )
1485 else:
1486 mh.ice_loc(self.location, "unexpected unary operation %s" % self.operator)
1488 def to_python_object(self):
1489 assert self.operator in (Unary_Operator.MINUS, Unary_Operator.PLUS)
1490 val = self.n_operand.to_python_object()
1491 if self.operator == Unary_Operator.MINUS:
1492 return -val
1493 else:
1494 return val
1496 def can_be_null(self):
1497 return False
1499 def uses_field_access(self):
1500 return self.n_operand.uses_field_access()
1503class Binary_Expression(Expression):
1504 """Expression with two operands
1506 This captures the following operations:
1508 * Binary_Operator.LOGICAL_AND (e.g. ``a and b``)
1509 * Binary_Operator.LOGICAL_OR (e.g. ``a or b``)
1510 * Binary_Operator.LOGICAL_XOR (e.g. ``a xor b``)
1511 * Binary_Operator.LOGICAL_IMPLIES (e.g. ``a implies b``)
1512 * Binary_Operator.COMP_EQ (e.g. ``a == null``)
1513 * Binary_Operator.COMP_NEQ (e.g. ``a != null``)
1514 * Binary_Operator.COMP_LT (e.g. ``1 < 2``)
1515 * Binary_Operator.COMP_LEQ (e.g. ``1 <= 2``)
1516 * Binary_Operator.COMP_GT (e.g. ``a > b``)
1517 * Binary_Operator.COMP_GEQ (e.g. ``a >= b``)
1518 * Binary_Operator.STRING_CONTAINS (e.g. ``"foo" in "foobar"``)
1519 * Binary_Operator.STRING_STARTSWITH (e.g. ``startswith("foo", "f")``)
1520 * Binary_Operator.STRING_ENDSWITH (e.g. ``endswith("foo", "o")``)
1521 * Binary_Operator.STRING_REGEX (e.g. ``matches("foo", ".o.``)
1522 * Binary_Operator.ARRAY_CONTAINS (e.g. ``42 in arr``)
1523 * Binary_Operator.PLUS (e.g. ``42 + b`` or ``"foo" + bar``)
1524 * Binary_Operator.MINUS (e.g. ``a - 1``)
1525 * Binary_Operator.TIMES (e.g. ``2 * x``)
1526 * Binary_Operator.DIVIDE (e.g. ``x / 2``)
1527 * Binary_Operator.REMAINDER (e.g. ``x % 2``)
1528 * Binary_Operator.POWER (e.g. ``x ** 2``)
1529 * Binary_Operator.INDEX (e.g. ``foo[2]``)
1531 Note that several builtin functions are mapped to unary operators.
1533 Note also that the plus operation is supported for integers,
1534 rationals and strings.
1536 :attribute operator: the operation
1537 :type: Binary_Operator
1539 :attribute n_lhs: the first operand
1540 :type: Expression
1542 :attribute n_rhs: the second operand
1543 :type: Expression
1545 """
1547 def __init__(self, mh, location, typ, operator, n_lhs, n_rhs):
1548 # lobster-trace: LRM.Expression
1549 # lobster-trace: LRM.Relation
1550 # lobster-trace: LRM.Simple_Expression
1551 # lobster-trace: LRM.Term
1552 # lobster-trace: LRM.Factor
1553 # lobster-trace: LRM.Signature_String_End_Functions
1554 # lobster-trace: LRM.Signature_Matches
1556 super().__init__(location, typ)
1557 assert isinstance(mh, Message_Handler)
1558 assert isinstance(operator, Binary_Operator)
1559 assert isinstance(n_lhs, Expression)
1560 assert isinstance(n_rhs, Expression)
1561 self.operator = operator
1562 self.n_lhs = n_lhs
1563 self.n_rhs = n_rhs
1565 if operator in (
1566 Binary_Operator.LOGICAL_AND,
1567 Binary_Operator.LOGICAL_OR,
1568 Binary_Operator.LOGICAL_XOR,
1569 Binary_Operator.LOGICAL_IMPLIES,
1570 ):
1571 self.n_lhs.ensure_type(mh, Builtin_Boolean)
1572 self.n_rhs.ensure_type(mh, Builtin_Boolean)
1574 elif operator in (Binary_Operator.COMP_EQ, Binary_Operator.COMP_NEQ):
1575 # lobster-trace: LRM.Union_Type_Equality
1576 # lobster-trace: LRM.Union_Type_Equality_Domain
1577 if (self.n_lhs.typ is None) or (self.n_rhs.typ is None):
1578 # We can compary anything to null (including itself)
1579 pass
1580 elif isinstance(self.n_lhs.typ, Union_Type) or isinstance(
1581 self.n_rhs.typ, Union_Type
1582 ):
1583 # For union types, we allow comparison if both
1584 # sides are record-like (Record_Type or Union_Type)
1585 lhs_is_record = isinstance(self.n_lhs.typ, (Record_Type, Union_Type))
1586 rhs_is_record = isinstance(self.n_rhs.typ, (Record_Type, Union_Type))
1587 if not (lhs_is_record and rhs_is_record): 1587 ↛ 1588line 1587 didn't jump to line 1588 because the condition on line 1587 was never true
1588 mh.error(
1589 self.location,
1590 "type mismatch: %s and %s do not match"
1591 % (self.n_lhs.typ.name, self.n_rhs.typ.name),
1592 )
1593 else:
1594 # Check that there is at least one pair of member
1595 # types (one from each side) where one is a subtype
1596 # of the other. This implements Equality_Domain for
1597 # union types: an unrelated record type is rejected.
1598 lhs_members = (
1599 self.n_lhs.typ.types
1600 if isinstance(self.n_lhs.typ, Union_Type)
1601 else [self.n_lhs.typ]
1602 )
1603 rhs_members = (
1604 self.n_rhs.typ.types
1605 if isinstance(self.n_rhs.typ, Union_Type)
1606 else [self.n_rhs.typ]
1607 )
1608 if not any(
1609 lm.is_subclass_of(rm) or rm.is_subclass_of(lm)
1610 for lm in lhs_members
1611 for rm in rhs_members
1612 ):
1613 mh.error(
1614 self.location,
1615 "type mismatch: %s and %s do not match"
1616 % (self.n_lhs.typ.name, self.n_rhs.typ.name),
1617 )
1618 elif self.n_lhs.typ != self.n_rhs.typ:
1619 # Otherwise we can compare anything, as long as the
1620 # types match
1621 mh.error(
1622 self.location,
1623 "type mismatch: %s and %s do not match"
1624 % (self.n_lhs.typ.name, self.n_rhs.typ.name),
1625 )
1627 elif operator in (
1628 Binary_Operator.COMP_LT,
1629 Binary_Operator.COMP_LEQ,
1630 Binary_Operator.COMP_GT,
1631 Binary_Operator.COMP_GEQ,
1632 ):
1633 self.n_lhs.ensure_type(mh, Builtin_Numeric_Type)
1634 self.n_rhs.ensure_type(mh, self.n_lhs.typ)
1636 elif operator in (
1637 Binary_Operator.STRING_CONTAINS,
1638 Binary_Operator.STRING_STARTSWITH,
1639 Binary_Operator.STRING_ENDSWITH,
1640 Binary_Operator.STRING_REGEX,
1641 ):
1642 self.n_lhs.ensure_type(mh, Builtin_String)
1643 self.n_rhs.ensure_type(mh, Builtin_String)
1645 elif operator == Binary_Operator.ARRAY_CONTAINS:
1646 self.n_rhs.ensure_type(mh, Array_Type)
1647 self.n_lhs.ensure_type(mh, self.n_rhs.typ.element_type.__class__)
1649 elif operator == Binary_Operator.PLUS:
1650 if isinstance(self.n_lhs.typ, Builtin_Numeric_Type):
1651 self.n_rhs.ensure_type(mh, self.n_lhs.typ)
1652 else:
1653 self.n_lhs.ensure_type(mh, Builtin_String)
1654 self.n_rhs.ensure_type(mh, Builtin_String)
1656 elif operator in (
1657 Binary_Operator.MINUS,
1658 Binary_Operator.TIMES,
1659 Binary_Operator.DIVIDE,
1660 ):
1661 self.n_lhs.ensure_type(mh, Builtin_Numeric_Type)
1662 self.n_rhs.ensure_type(mh, self.n_lhs.typ)
1664 elif operator == Binary_Operator.POWER:
1665 self.n_lhs.ensure_type(mh, Builtin_Numeric_Type)
1666 self.n_rhs.ensure_type(mh, Builtin_Integer)
1668 elif operator == Binary_Operator.REMAINDER:
1669 self.n_lhs.ensure_type(mh, Builtin_Integer)
1670 self.n_rhs.ensure_type(mh, Builtin_Integer)
1672 elif operator == Binary_Operator.INDEX:
1673 self.n_lhs.ensure_type(mh, Array_Type)
1674 self.n_rhs.ensure_type(mh, Builtin_Integer)
1676 else:
1677 mh.ice_loc(self.location, "unexpected binary operation %s" % operator)
1679 def dump(self, indent=0): # pragma: no cover
1680 # lobster-exclude: Debugging feature
1681 self.write_indent(indent, f"Binary {self.operator} Expression")
1682 self.write_indent(indent + 1, f"Type: {self.typ.name}")
1683 self.n_lhs.dump(indent + 1)
1684 self.n_rhs.dump(indent + 1)
1686 def to_string(self):
1687 infix_operators = {
1688 Binary_Operator.LOGICAL_AND: "and",
1689 Binary_Operator.LOGICAL_OR: "or",
1690 Binary_Operator.LOGICAL_XOR: "xor",
1691 Binary_Operator.LOGICAL_IMPLIES: "implies",
1692 Binary_Operator.COMP_EQ: "==",
1693 Binary_Operator.COMP_NEQ: "!=",
1694 Binary_Operator.COMP_LT: "<",
1695 Binary_Operator.COMP_LEQ: "<=",
1696 Binary_Operator.COMP_GT: ">",
1697 Binary_Operator.COMP_GEQ: ">=",
1698 Binary_Operator.STRING_CONTAINS: "in",
1699 Binary_Operator.ARRAY_CONTAINS: "in",
1700 Binary_Operator.PLUS: "+",
1701 Binary_Operator.MINUS: "-",
1702 Binary_Operator.TIMES: "*",
1703 Binary_Operator.DIVIDE: "/",
1704 Binary_Operator.REMAINDER: "%",
1705 Binary_Operator.POWER: "**",
1706 }
1707 string_functions = {
1708 Binary_Operator.STRING_STARTSWITH: "startswith",
1709 Binary_Operator.STRING_ENDSWITH: "endswith",
1710 Binary_Operator.STRING_REGEX: "matches",
1711 }
1713 if self.operator in infix_operators:
1714 return "%s %s %s" % (
1715 self.n_lhs.to_string(),
1716 infix_operators[self.operator],
1717 self.n_rhs.to_string(),
1718 )
1720 elif self.operator in string_functions:
1721 return "%s(%s, %s)" % (
1722 string_functions[self.operator],
1723 self.n_lhs.to_string(),
1724 self.n_rhs.to_string(),
1725 )
1727 elif self.operator == Binary_Operator.INDEX:
1728 return "%s[%s]" % (self.n_lhs.to_string(), self.n_rhs.to_string())
1730 else:
1731 assert False
1733 def evaluate(self, mh, context, gstab):
1734 # lobster-trace: LRM.Null_Equivalence
1735 # lobster-trace: LRM.Null_Is_Invalid
1736 # lobster-trace: LRM.Signature_String_End_Functions
1737 # lobster-trace: LRM.Signature_Matches
1738 # lobster-trace: LRM.Startswith_Semantics
1739 # lobster-trace: LRM.Endswith_Semantics
1740 # lobster-trace: LRM.Matches_Semantics
1742 assert isinstance(mh, Message_Handler)
1743 assert context is None or isinstance(context, dict)
1744 assert gstab is None or isinstance(gstab, Symbol_Table)
1746 v_lhs = self.n_lhs.evaluate(mh, context, gstab)
1747 if v_lhs.value is None and self.operator not in (
1748 Binary_Operator.COMP_EQ,
1749 Binary_Operator.COMP_NEQ,
1750 ):
1751 mh.error(
1752 v_lhs.location,
1753 "lhs of check %s (%s) must not be null"
1754 % (self.to_string(), mh.cross_file_reference(self.location)),
1755 )
1757 # Check for the short-circuit operators first
1758 if self.operator == Binary_Operator.LOGICAL_AND:
1759 assert isinstance(v_lhs.value, bool)
1760 if v_lhs.value:
1761 return self.n_rhs.evaluate(mh, context, gstab)
1762 else:
1763 return v_lhs
1765 elif self.operator == Binary_Operator.LOGICAL_OR:
1766 assert isinstance(v_lhs.value, bool)
1767 if v_lhs.value:
1768 return v_lhs
1769 else:
1770 return self.n_rhs.evaluate(mh, context, gstab)
1772 elif self.operator == Binary_Operator.LOGICAL_IMPLIES:
1773 assert isinstance(v_lhs.value, bool)
1774 if v_lhs.value:
1775 return self.n_rhs.evaluate(mh, context, gstab)
1776 else:
1777 return Value(location=self.location, value=True, typ=self.typ)
1779 # Otherwise, evaluate RHS and do the operation
1780 v_rhs = self.n_rhs.evaluate(mh, context, gstab)
1781 if v_rhs.value is None and self.operator not in (
1782 Binary_Operator.COMP_EQ,
1783 Binary_Operator.COMP_NEQ,
1784 ):
1785 mh.error(
1786 v_rhs.location,
1787 "rhs of check %s (%s) must not be null"
1788 % (self.to_string(), mh.cross_file_reference(self.location)),
1789 )
1791 if self.operator == Binary_Operator.LOGICAL_XOR:
1792 assert isinstance(v_lhs.value, bool)
1793 assert isinstance(v_rhs.value, bool)
1794 return Value(
1795 location=self.location, value=v_lhs.value ^ v_rhs.value, typ=self.typ
1796 )
1798 elif self.operator == Binary_Operator.COMP_EQ:
1799 return Value(
1800 location=self.location, value=v_lhs.value == v_rhs.value, typ=self.typ
1801 )
1803 elif self.operator == Binary_Operator.COMP_NEQ:
1804 return Value(
1805 location=self.location, value=v_lhs.value != v_rhs.value, typ=self.typ
1806 )
1808 elif self.operator in (
1809 Binary_Operator.COMP_LT,
1810 Binary_Operator.COMP_LEQ,
1811 Binary_Operator.COMP_GT,
1812 Binary_Operator.COMP_GEQ,
1813 ):
1814 return Value(
1815 location=self.location,
1816 value={
1817 Binary_Operator.COMP_LT: lambda lhs, rhs: lhs < rhs,
1818 Binary_Operator.COMP_LEQ: lambda lhs, rhs: lhs <= rhs,
1819 Binary_Operator.COMP_GT: lambda lhs, rhs: lhs > rhs,
1820 Binary_Operator.COMP_GEQ: lambda lhs, rhs: lhs >= rhs,
1821 }[self.operator](v_lhs.value, v_rhs.value),
1822 typ=self.typ,
1823 )
1825 elif self.operator == Binary_Operator.STRING_CONTAINS:
1826 assert isinstance(v_lhs.value, str)
1827 assert isinstance(v_rhs.value, str)
1829 return Value(
1830 location=self.location, value=v_lhs.value in v_rhs.value, typ=self.typ
1831 )
1833 elif self.operator == Binary_Operator.STRING_STARTSWITH:
1834 assert isinstance(v_lhs.value, str)
1835 assert isinstance(v_rhs.value, str)
1836 return Value(
1837 location=self.location,
1838 value=v_lhs.value.startswith(v_rhs.value),
1839 typ=self.typ,
1840 )
1842 elif self.operator == Binary_Operator.STRING_ENDSWITH:
1843 assert isinstance(v_lhs.value, str)
1844 assert isinstance(v_rhs.value, str)
1845 return Value(
1846 location=self.location,
1847 value=v_lhs.value.endswith(v_rhs.value),
1848 typ=self.typ,
1849 )
1851 elif self.operator == Binary_Operator.STRING_REGEX:
1852 assert isinstance(v_lhs.value, str)
1853 assert isinstance(v_rhs.value, str)
1854 return Value(
1855 location=self.location,
1856 value=re.match(v_rhs.value, v_lhs.value) is not None,
1857 typ=self.typ,
1858 )
1860 elif self.operator == Binary_Operator.ARRAY_CONTAINS:
1861 assert isinstance(v_rhs.value, list)
1863 return Value(
1864 location=self.location, value=v_lhs in v_rhs.value, typ=self.typ
1865 )
1867 elif self.operator == Binary_Operator.PLUS:
1868 assert isinstance(v_lhs.value, (int, str, Fraction))
1869 assert isinstance(v_rhs.value, (int, str, Fraction))
1870 return Value(
1871 location=self.location, value=v_lhs.value + v_rhs.value, typ=self.typ
1872 )
1874 elif self.operator == Binary_Operator.MINUS:
1875 assert isinstance(v_lhs.value, (int, Fraction))
1876 assert isinstance(v_rhs.value, (int, Fraction))
1877 return Value(
1878 location=self.location, value=v_lhs.value - v_rhs.value, typ=self.typ
1879 )
1881 elif self.operator == Binary_Operator.TIMES:
1882 assert isinstance(v_lhs.value, (int, Fraction))
1883 assert isinstance(v_rhs.value, (int, Fraction))
1884 return Value(
1885 location=self.location, value=v_lhs.value * v_rhs.value, typ=self.typ
1886 )
1888 elif self.operator == Binary_Operator.DIVIDE:
1889 assert isinstance(v_lhs.value, (int, Fraction))
1890 assert isinstance(v_rhs.value, (int, Fraction))
1892 if v_rhs.value == 0: 1892 ↛ 1893line 1892 didn't jump to line 1893 because the condition on line 1892 was never true
1893 mh.error(
1894 v_rhs.location,
1895 "division by zero in %s (%s)"
1896 % (self.to_string(), mh.cross_file_reference(self.location)),
1897 )
1899 if isinstance(v_lhs.value, int):
1900 return Value(
1901 location=self.location,
1902 value=v_lhs.value // v_rhs.value,
1903 typ=self.typ,
1904 )
1905 else:
1906 return Value(
1907 location=self.location,
1908 value=v_lhs.value / v_rhs.value,
1909 typ=self.typ,
1910 )
1912 elif self.operator == Binary_Operator.REMAINDER:
1913 assert isinstance(v_lhs.value, int)
1914 assert isinstance(v_rhs.value, int)
1916 if v_rhs.value == 0: 1916 ↛ 1917line 1916 didn't jump to line 1917 because the condition on line 1916 was never true
1917 mh.error(
1918 v_rhs.location,
1919 "division by zero in %s (%s)"
1920 % (self.to_string(), mh.cross_file_reference(self.location)),
1921 )
1923 return Value(
1924 location=self.location,
1925 value=math.remainder(v_lhs.value, v_rhs.value),
1926 typ=self.typ,
1927 )
1929 elif self.operator == Binary_Operator.POWER:
1930 assert isinstance(v_lhs.value, (int, Fraction))
1931 assert isinstance(v_rhs.value, int)
1932 return Value(
1933 location=self.location, value=v_lhs.value**v_rhs.value, typ=self.typ
1934 )
1936 elif self.operator == Binary_Operator.INDEX:
1937 assert isinstance(v_lhs.value, list)
1938 assert isinstance(v_rhs.value, int)
1940 if v_rhs.value < 0: 1940 ↛ 1941line 1940 didn't jump to line 1941 because the condition on line 1940 was never true
1941 mh.error(
1942 v_rhs.location,
1943 "index cannot be less than zero in %s (%s)"
1944 % (self.to_string(), mh.cross_file_reference(self.location)),
1945 )
1946 elif ( 1946 ↛ 1950line 1946 didn't jump to line 1950 because the condition on line 1946 was never true
1947 v_lhs.typ.upper_bound is not None
1948 and v_rhs.value > v_lhs.typ.upper_bound
1949 ):
1950 mh.error(
1951 v_rhs.location,
1952 "index cannot be more than %u in %s (%s)"
1953 % (
1954 v_lhs.typ.upper_bound,
1955 self.to_string(),
1956 mh.cross_file_reference(self.location),
1957 ),
1958 )
1959 elif v_rhs.value > len(v_lhs.value): 1959 ↛ 1960line 1959 didn't jump to line 1960 because the condition on line 1959 was never true
1960 mh.error(
1961 v_lhs.location,
1962 "array is not big enough in %s (%s)"
1963 % (self.to_string(), mh.cross_file_reference(self.location)),
1964 )
1966 return Value(
1967 location=self.location,
1968 value=v_lhs.value[v_rhs.value].value,
1969 typ=self.typ,
1970 )
1972 else:
1973 mh.ice_loc(self.location, "unexpected binary operator %s" % self.operator)
1975 def can_be_null(self):
1976 return False
1978 def uses_field_access(self):
1979 return self.n_lhs.uses_field_access() or self.n_rhs.uses_field_access()
1982class Field_Access_Expression(Expression):
1983 """Tuple, Record, or Union field access
1985 For example in::
1987 foo.bar
1988 ^1 ^2
1990 :attribute n_prefix: expression with tuple, record, or union type (see 1)
1991 :type: Expression
1993 :attribute n_field: a field to dereference (see 2)
1994 :type: Composite_Component
1996 :attribute is_union_access: True if the prefix is a union type
1997 :type: bool
1999 :attribute is_universal: True if field exists in all union members.
2000 Only meaningful when is_union_access is True.
2001 :type: bool
2003 """
2005 def __init__(
2006 self, mh, location, n_prefix, n_field, is_union_access=False, is_universal=True
2007 ):
2008 # lobster-trace: LRM.Union_Type_Field_Access
2009 assert isinstance(mh, Message_Handler)
2010 assert isinstance(n_prefix, Expression)
2011 assert isinstance(n_field, Composite_Component)
2012 assert isinstance(is_union_access, bool)
2013 assert isinstance(is_universal, bool)
2014 super().__init__(location, n_field.n_typ)
2015 self.n_prefix = n_prefix
2016 self.n_field = n_field
2017 self.is_union_access = is_union_access
2018 self.is_universal = is_universal
2020 if not is_union_access:
2021 self.n_prefix.ensure_type(mh, self.n_field.member_of)
2023 def dump(self, indent=0): # pragma: no cover
2024 # lobster-exclude: Debugging feature
2025 self.write_indent(indent, f"Field_Access ({self.n_field.name})")
2026 self.n_prefix.dump(indent + 1)
2028 def to_string(self):
2029 return self.n_prefix.to_string() + "." + self.n_field.name
2031 def evaluate(self, mh, context, gstab):
2032 assert isinstance(mh, Message_Handler)
2033 assert context is None or isinstance(context, dict)
2034 assert gstab is None or isinstance(gstab, Symbol_Table)
2036 v_prefix = self.n_prefix.evaluate(mh, context, gstab).value
2037 if v_prefix is None:
2038 # lobster-trace: LRM.Dereference
2039 mh.error(self.n_prefix.location, "null dereference")
2041 # lobster-trace: LRM.Union_Type_Partial_Field_Access
2042 # lobster-trace: LRM.Union_Type_Partial_Field_Null
2043 if self.n_field.name not in v_prefix:
2044 return Value(self.location, None, None)
2046 v_field = v_prefix[self.n_field.name]
2047 if isinstance(v_field, Expression):
2048 # lobster-trace: LRM.Dereference
2049 return v_field.evaluate(mh, context, gstab)
2050 else:
2051 return v_field
2053 def can_be_null(self):
2054 # A union field access on a partial field (not present in all
2055 # member types) evaluates to null at runtime, so we must
2056 # report True in that case.
2057 return self.is_union_access and not self.is_universal
2059 def uses_field_access(self):
2060 # lobster-trace: LRM.Dereference
2061 if isinstance(self.n_prefix.typ, (Record_Type, Union_Type)):
2062 return True
2063 return self.n_prefix.uses_field_access()
2066class Range_Test(Expression):
2067 """Range membership test
2069 For example in::
2071 x in 1 .. field+1
2072 ^lhs ^lower ^^^^^^^upper
2074 Note that none of these are guaranteed to be literals or names;
2075 you can have arbitrarily complex expressions here.
2077 :attribute n_lhs: the expression to test
2078 :type: Expression
2080 :attribute n_lower: the lower bound
2081 :type: Expression
2083 :attribute n_upper: the upper bound
2084 :type: Expression
2086 """
2088 def __init__(self, mh, location, typ, n_lhs, n_lower, n_upper):
2089 # lobster-trace: LRM.Relation
2090 super().__init__(location, typ)
2091 assert isinstance(mh, Message_Handler)
2092 assert isinstance(n_lhs, Expression)
2093 assert isinstance(n_lower, Expression)
2094 assert isinstance(n_upper, Expression)
2095 self.n_lhs = n_lhs
2096 self.n_lower = n_lower
2097 self.n_upper = n_upper
2099 self.n_lhs.ensure_type(mh, Builtin_Numeric_Type)
2100 self.n_lower.ensure_type(mh, self.n_lhs.typ)
2101 self.n_upper.ensure_type(mh, self.n_lhs.typ)
2103 def to_string(self):
2104 return "%s in %s .. %s" % (
2105 self.n_lhs.to_string(),
2106 self.n_lower.to_string(),
2107 self.n_upper.to_string(),
2108 )
2110 def dump(self, indent=0): # pragma: no cover
2111 # lobster-exclude: Debugging feature
2112 self.write_indent(indent, "Range Test")
2113 self.write_indent(indent + 1, f"Type: {self.typ}")
2114 self.n_lhs.dump(indent + 1)
2115 self.n_lower.dump(indent + 1)
2116 self.n_upper.dump(indent + 1)
2118 def evaluate(self, mh, context, gstab):
2119 # lobster-trace: LRM.Null_Is_Invalid
2120 assert isinstance(mh, Message_Handler)
2121 assert context is None or isinstance(context, dict)
2122 assert gstab is None or isinstance(gstab, Symbol_Table)
2124 v_lhs = self.n_lhs.evaluate(mh, context, gstab)
2125 if v_lhs.value is None: 2125 ↛ 2126line 2125 didn't jump to line 2126 because the condition on line 2125 was never true
2126 mh.error(
2127 v_lhs.location,
2128 "lhs of range check %s (%s) see must not be null"
2129 % (self.to_string(), mh.cross_file_reference(self.location)),
2130 )
2132 v_lower = self.n_lower.evaluate(mh, context, gstab)
2133 if v_lower.value is None: 2133 ↛ 2134line 2133 didn't jump to line 2134 because the condition on line 2133 was never true
2134 mh.error(
2135 v_lower.location,
2136 "lower bound of range check %s (%s) must not be null"
2137 % (self.to_string(), mh.cross_file_reference(self.location)),
2138 )
2140 v_upper = self.n_upper.evaluate(mh, context, gstab)
2141 if v_upper.value is None: 2141 ↛ 2142line 2141 didn't jump to line 2142 because the condition on line 2141 was never true
2142 mh.error(
2143 v_upper.location,
2144 "upper bound of range check %s (%s) must not be null"
2145 % (self.to_string(), mh.cross_file_reference(self.location)),
2146 )
2148 return Value(
2149 location=self.location,
2150 value=v_lower.value <= v_lhs.value <= v_upper.value,
2151 typ=self.typ,
2152 )
2154 def can_be_null(self):
2155 return False
2157 def uses_field_access(self):
2158 return (
2159 self.n_lhs.uses_field_access()
2160 or self.n_lower.uses_field_access()
2161 or self.n_upper.uses_field_access()
2162 )
2165class OneOf_Expression(Expression):
2166 """OneOf expression
2168 For example in::
2170 oneof(a, b, c)
2171 ^^^^^^^ choices
2173 :attribute choices: a list of boolean expressions to test
2174 :type: list[Expression]
2175 """
2177 def __init__(self, mh, location, typ, choices):
2178 # lobster-trace: LRM.Signature_OneOf
2179 super().__init__(location, typ)
2180 assert isinstance(typ, Builtin_Boolean)
2181 assert isinstance(mh, Message_Handler)
2182 assert isinstance(choices, list)
2183 assert all(isinstance(item, Expression) for item in choices)
2184 self.choices = choices
2186 for n_choice in choices:
2187 n_choice.ensure_type(mh, Builtin_Boolean)
2189 def to_string(self):
2190 return "oneof(%s)" % ", ".join(
2191 n_choice.to_string() for n_choice in self.choices
2192 )
2194 def dump(self, indent=0): # pragma: no cover
2195 # lobster-exclude: Debugging feature
2196 self.write_indent(indent, "OneOf Test")
2197 self.write_indent(indent + 1, f"Type: {self.typ}")
2198 for n_choice in self.choices:
2199 n_choice.dump(indent + 1)
2201 def evaluate(self, mh, context, gstab):
2202 # lobster-trace: LRM.OneOf_Semantics
2203 assert isinstance(mh, Message_Handler)
2204 assert context is None or isinstance(context, dict)
2205 assert gstab is None or isinstance(gstab, Symbol_Table)
2207 v_choices = [
2208 n_choice.evaluate(mh, context, gstab).value for n_choice in self.choices
2209 ]
2211 return Value(
2212 location=self.location, value=v_choices.count(True) == 1, typ=self.typ
2213 )
2215 def can_be_null(self):
2216 return False
2218 def uses_field_access(self):
2219 return any(n_choice.uses_field_access() for n_choice in self.choices)
2222class Action(Node):
2223 """An if or elseif part inside a conditional expression
2225 Each :class:`Conditional_Expression` is made up of a sequence of
2226 Actions. For example here is a single expression with two
2227 Actions::
2229 (if x == 0 then "zero" elsif x == 1 then "one" else "lots")
2230 ^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^
2232 Note that the else part is not an action, it is an attribute of
2233 the :class:`Conditional_Expression` itself.
2235 :attribute kind: Either if or elseif
2236 :type: str
2238 :attribute n_cond: The boolean condition expression
2239 :type: Expression
2241 :attribute n_expr: The value if the condition evaluates to true
2242 :type: Expression
2244 """
2246 def __init__(self, mh, t_kind, n_condition, n_expression):
2247 # lobster-trace: LRM.Conditional_Expression
2248 assert isinstance(mh, Message_Handler)
2249 assert isinstance(t_kind, Token)
2250 assert t_kind.kind == "KEYWORD"
2251 assert t_kind.value in ("if", "elsif")
2252 assert isinstance(n_condition, Expression)
2253 assert isinstance(n_expression, Expression)
2254 super().__init__(t_kind.location)
2255 self.kind = t_kind.value
2256 self.n_cond = n_condition
2257 self.n_expr = n_expression
2258 # lobster-trace: LRM.Conditional_Expression_Types
2259 self.n_cond.ensure_type(mh, Builtin_Boolean)
2261 def dump(self, indent=0): # pragma: no cover
2262 # lobster-exclude: Debugging feature
2263 self.write_indent(indent, f"{self.kind.capitalize()} Action")
2264 self.write_indent(indent + 1, "Condition")
2265 self.n_cond.dump(indent + 2)
2266 self.write_indent(indent + 1, "Value")
2267 self.n_expr.dump(indent + 2)
2269 def to_string(self):
2270 return "%s %s then %s" % (
2271 self.kind,
2272 self.n_cond.to_string(),
2273 self.n_expr.to_string(),
2274 )
2277class Conditional_Expression(Expression):
2278 """A conditional expression
2280 Each :class:`Conditional_Expression` is made up of a sequence of
2281 one or more :class:`Action`. For example here is a single
2282 expression with two Actions::
2284 (if x == 0 then "zero" elsif x == 1 then "one" else "lots")
2285 ^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^
2287 The else expression is part of the conditional expression itself.
2289 A conditional expression will have at least one action (the if
2290 action), and all other actions will be elsif actions. The else
2291 expression is not optional and will always be present. The types
2292 of all actions and the else expression will match.
2294 :attribute actions: a list of Actions
2295 :type: list[Action]
2297 :attribute else_expr: the else expression
2298 :type: Expression
2300 """
2302 def __init__(self, location, if_action):
2303 # lobster-trace: LRM.Conditional_Expression
2304 assert isinstance(if_action, Action)
2305 assert if_action.kind == "if"
2306 super().__init__(location, if_action.n_expr.typ)
2307 self.actions = [if_action]
2308 self.else_expr = None
2310 def add_elsif(self, mh, n_action):
2311 # lobster-trace: LRM.Conditional_Expression
2312 # lobster-trace; LRM.Conditional_Expression_Types
2313 assert isinstance(mh, Message_Handler)
2314 assert isinstance(n_action, Action)
2315 assert n_action.kind == "elsif"
2317 n_action.n_expr.ensure_type(mh, self.typ)
2318 self.actions.append(n_action)
2320 def set_else_part(self, mh, n_expr):
2321 # lobster-trace: LRM.Conditional_Expression
2322 # lobster-trace; LRM.Conditional_Expression_Types
2323 assert isinstance(mh, Message_Handler)
2324 assert isinstance(n_expr, Expression)
2326 n_expr.ensure_type(mh, self.typ)
2327 self.else_expr = n_expr
2329 def dump(self, indent=0): # pragma: no cover
2330 # lobster-exclude: Debugging feature
2331 self.write_indent(indent, "Conditional expression")
2332 for action in self.actions:
2333 action.dump(indent + 1)
2334 self.write_indent(indent + 1, "Else")
2335 self.else_expr.dump(indent + 2)
2337 def to_string(self):
2338 rv = "(" + " ".join(action.to_string() for action in self.actions)
2339 rv += " else %s" % self.else_expr.to_string()
2340 rv += ")"
2341 return rv
2343 def evaluate(self, mh, context, gstab):
2344 # lobster-trace: LRM.Conditional_Expression_Else
2345 # lobster-trace: LRM.Conditional_Expression_Evaluation
2346 # lobster-trace: LRM.Null_Is_Invalid
2347 assert isinstance(mh, Message_Handler)
2348 assert context is None or isinstance(context, dict)
2349 assert gstab is None or isinstance(gstab, Symbol_Table)
2351 for action in self.actions:
2352 v_cond = action.n_cond.evaluate(mh, context, gstab)
2353 if v_cond.value is None: 2353 ↛ 2354line 2353 didn't jump to line 2354 because the condition on line 2353 was never true
2354 mh.error(
2355 v_cond.location,
2356 "condition of %s (%s) must not be null"
2357 % (action.to_string(), mh.cross_file_reference(self.location)),
2358 )
2359 if v_cond.value:
2360 return action.n_expr.evaluate(mh, context, gstab)
2362 return self.else_expr.evaluate(mh, context, gstab)
2364 def can_be_null(self):
2365 if self.else_expr and self.else_expr.can_be_null():
2366 return True
2368 return any(action.n_expr.can_be_null() for action in self.actions)
2370 def uses_field_access(self):
2371 return any(
2372 action.n_cond.uses_field_access() or action.n_expr.uses_field_access()
2373 for action in self.actions
2374 ) or (self.else_expr is not None and self.else_expr.uses_field_access())
2377class Quantified_Expression(Expression):
2378 """A quantified expression
2380 For example::
2382 (forall x in array_component => x > 0)
2383 ^4 ^1 ^2 ^^^^^3
2385 A quantified expression introduces and binds a
2386 :class:`Quantified_Variable` (see 1) from a specified source (see
2387 2). When the body (see 3) is evaluated, the name of 1 is bound to
2388 each component of the source in turn.
2390 :attribute n_var: The quantified variable (see 1)
2391 :type: Quantified_Variable
2393 :attribute n_source: The array to iterate over (see 2)
2394 :type: Name_Reference
2396 :attribute n_expr: The body of the quantifier (see 3)
2397 :type: Expression
2399 :attribute universal: True means forall, false means exists (see 4)
2400 :type: Boolean
2402 """
2404 def __init__(self, mh, location, typ, universal, n_variable, n_source, n_expr):
2405 # lobster-trace: LRM.Quantified_Expression
2406 # lobster-trace: LRM.Quantification_Type
2407 super().__init__(location, typ)
2408 assert isinstance(typ, Builtin_Boolean)
2409 assert isinstance(universal, bool)
2410 assert isinstance(n_variable, Quantified_Variable)
2411 assert isinstance(n_expr, Expression)
2412 assert isinstance(n_source, Name_Reference)
2413 self.universal = universal
2414 self.n_var = n_variable
2415 self.n_expr = n_expr
2416 self.n_source = n_source
2417 self.n_expr.ensure_type(mh, Builtin_Boolean)
2419 def dump(self, indent=0): # pragma: no cover
2420 # lobster-exclude: Debugging feature
2421 if self.universal:
2422 self.write_indent(indent, "Universal quantified expression")
2423 else:
2424 self.write_indent(indent, "Existential quantified expression")
2425 self.n_var.dump(indent + 1)
2426 self.n_expr.dump(indent + 1)
2428 def to_string(self):
2429 return "(%s %s in %s => %s)" % (
2430 "forall" if self.universal else "exists",
2431 self.n_var.name,
2432 self.n_source.to_string(),
2433 self.n_expr.to_string(),
2434 )
2436 def evaluate(self, mh, context, gstab):
2437 # lobster-trace: LRM.Null_Is_Invalid
2438 # lobster-trace: LRM.Universal_Quantification_Semantics
2439 # lobster-trace: LRM.Existential_Quantification_Semantics
2440 assert isinstance(mh, Message_Handler)
2441 assert context is None or isinstance(context, dict)
2442 assert gstab is None or isinstance(gstab, Symbol_Table)
2444 if context is None: 2444 ↛ 2445line 2444 didn't jump to line 2445 because the condition on line 2444 was never true
2445 new_ctx = {}
2446 else:
2447 new_ctx = copy(context)
2449 # This is going to be a bit tricky. We essentially eliminate
2450 # the quantifier and substitute; for the sake of making better
2451 # error messages.
2452 assert isinstance(self.n_source.entity, Composite_Component)
2453 array_values = context[self.n_source.entity.name]
2454 if isinstance(array_values, Implicit_Null):
2455 mh.error(
2456 array_values.location,
2457 "%s in quantified expression %s (%s) "
2458 "must not be null"
2459 % (
2460 self.n_source.to_string(),
2461 self.to_string(),
2462 mh.cross_file_reference(self.location),
2463 ),
2464 )
2465 else:
2466 assert isinstance(array_values, Array_Aggregate)
2468 rv = self.universal
2469 loc = self.location
2470 for binding in array_values.value:
2471 new_ctx[self.n_var.name] = binding
2472 result = self.n_expr.evaluate(mh, new_ctx, gstab)
2473 assert isinstance(result.value, bool)
2474 if self.universal and not result.value:
2475 rv = False
2476 loc = binding.location
2477 break
2478 elif not self.universal and result.value:
2479 rv = True
2480 loc = binding.location
2481 break
2483 return Value(location=loc, value=rv, typ=self.typ)
2485 def can_be_null(self):
2486 return False
2488 def uses_field_access(self):
2489 return self.n_source.uses_field_access() or self.n_expr.uses_field_access()
2492##############################################################################
2493# AST Nodes (Entities)
2494##############################################################################
2497class Entity(Node, metaclass=ABCMeta):
2498 """Base class for all entities.
2500 An entity is a concrete object (with a name) for which we need to
2501 allocate memory. Examples of entities are types and record
2502 objects.
2504 :attribute name: unqualified name of the entity
2505 :type: str
2507 """
2509 def __init__(self, name, location):
2510 # lobster-trace: LRM.Described_Name_Equality
2511 super().__init__(location)
2512 assert isinstance(name, str)
2513 self.name = name
2516class Typed_Entity(Entity, metaclass=ABCMeta):
2517 """Base class for entities with a type.
2519 A typed entity is a concrete object (with a name and TRLC type)
2520 for which we need to allocate memory. Examples of typed entities
2521 are record objects and components.
2523 :attribute n_typ: type of the entity
2524 :type: Type
2526 """
2528 def __init__(self, name, location, n_typ):
2529 # lobster-exclude: Constructor only declares variables
2530 super().__init__(name, location)
2531 assert isinstance(n_typ, Type)
2532 self.n_typ = n_typ
2535class Quantified_Variable(Typed_Entity):
2536 """Variable used in quantified expression.
2538 A quantified expression declares and binds a variable, for which
2539 we need a named entity. For example in::
2541 (forall x in array => x > 1)
2542 ^
2544 We represent this first x as a :class:`Quantified_Variable`, the
2545 second x will be an ordinary :class:`Name_Reference`.
2547 :attribute typ: type of the variable (i.e. element type of the array)
2548 :type: Type
2550 """
2552 def dump(self, indent=0): # pragma: no cover
2553 # lobster-exclude: Debugging feature
2554 self.write_indent(indent, f"Quantified Variable {self.name}")
2555 self.n_typ.dump(indent + 1)
2558class Type(Entity, metaclass=ABCMeta):
2559 """Abstract base class for all types."""
2561 def perform_type_checks(self, mh, value, gstab):
2562 assert isinstance(mh, Message_Handler)
2563 assert isinstance(value, Expression)
2564 assert isinstance(gstab, Symbol_Table)
2565 return True
2567 def get_example_value(self):
2568 # lobster-exclude: utility method
2569 assert False
2572class Concrete_Type(Type, metaclass=ABCMeta):
2573 # lobster-trace: LRM.Type_Declarations
2574 """Abstract base class for all non-anonymous types.
2576 :attribute n_package: package where this type was declared
2577 :type: Package
2578 """
2580 def __init__(self, name, location, n_package):
2581 super().__init__(name, location)
2582 assert isinstance(n_package, Package)
2583 self.n_package = n_package
2585 def fully_qualified_name(self):
2586 """Return the FQN for this type (i.e. PACKAGE.NAME)
2588 :returns: the type's full name
2589 :rtype: str
2590 """
2591 return self.n_package.name + "." + self.name
2593 def __hash__(self):
2594 return hash((self.n_package.name, self.name))
2596 def __repr__(self):
2597 return "%s<%s>" % (self.__class__.__name__, self.fully_qualified_name())
2600class Builtin_Type(Type, metaclass=ABCMeta):
2601 # lobster-trace: LRM.Builtin_Types
2602 """Abstract base class for all builtin types."""
2604 LOCATION = Location(file_name="<builtin>")
2606 def __init__(self, name):
2607 super().__init__(name, Builtin_Type.LOCATION)
2609 def dump(self, indent=0): # pragma: no cover
2610 self.write_indent(indent, self.__class__.__name__)
2613class Builtin_Numeric_Type(Builtin_Type, metaclass=ABCMeta):
2614 # lobster-trace: LRM.Builtin_Types
2615 """Abstract base class for all builtin numeric types."""
2617 def dump(self, indent=0): # pragma: no cover
2618 self.write_indent(indent, self.__class__.__name__)
2621class Builtin_Function(Entity):
2622 # lobster-trace: LRM.Builtin_Functions
2623 """Builtin functions.
2625 These are auto-generated by the :class:`~trlc.trlc.Source_Manager`.
2627 :attribute arity: number of parameters
2628 :type: int
2630 :attribute arity_at_least: when true, arity indicates a lower bound
2631 :type: bool
2633 """
2635 LOCATION = Location(file_name="<builtin>")
2637 def __init__(self, name, arity, arity_at_least=False):
2638 super().__init__(name, Builtin_Function.LOCATION)
2639 assert isinstance(arity, int)
2640 assert isinstance(arity_at_least, bool)
2641 assert arity >= 0
2642 self.arity = arity
2643 self.arity_at_least = arity_at_least
2645 def dump(self, indent=0): # pragma: no cover
2646 self.write_indent(indent, self.__class__.__name__ + " " + self.name)
2649class Array_Type(Type):
2650 """Anonymous array type.
2652 These are declared implicitly for each record component that has
2653 an array specifier::
2655 foo Integer [5 .. *]
2656 ^
2658 :attribute lower_bound: minimum number of elements
2659 :type: int
2661 :attribute loc_lower: text location of the lower bound indicator
2662 :type: Location
2664 :attribute upper_bound: maximum number of elements (or None)
2665 :type: int
2667 :attribute loc_upper: text location of the upper bound indicator
2668 :type: Location
2670 :attribute element_type: type of the array elements
2671 :type: Type
2673 """
2675 def __init__(
2676 self, location, element_type, loc_lower, lower_bound, loc_upper, upper_bound
2677 ):
2678 # lobster-exclude: Constructor only declares variables
2679 assert isinstance(element_type, Type) or element_type is None
2680 assert isinstance(lower_bound, int)
2681 assert lower_bound >= 0
2682 assert upper_bound is None or isinstance(upper_bound, int)
2683 assert upper_bound is None or upper_bound >= 0
2684 assert isinstance(loc_lower, Location)
2685 assert isinstance(loc_upper, Location)
2687 if element_type is None: 2687 ↛ 2688line 2687 didn't jump to line 2688 because the condition on line 2687 was never true
2688 name = "universal array"
2689 elif upper_bound is None:
2690 if lower_bound == 0:
2691 name = "array of %s" % element_type.name
2692 else:
2693 name = "array of at least %u %s" % (lower_bound, element_type.name)
2694 elif lower_bound == upper_bound:
2695 name = "array of %u %s" % (lower_bound, element_type.name)
2696 else:
2697 name = "array of %u to %u %s" % (
2698 lower_bound,
2699 upper_bound,
2700 element_type.name,
2701 )
2702 super().__init__(name, location)
2703 self.lower_bound = lower_bound
2704 self.loc_lower = loc_lower
2705 self.upper_bound = upper_bound
2706 self.loc_upper = loc_upper
2707 self.element_type = element_type
2709 def dump(self, indent=0): # pragma: no cover
2710 # lobster-exclude: Debugging feature
2711 self.write_indent(indent, "Array_Type")
2712 self.write_indent(indent + 1, f"Lower bound: {self.lower_bound}")
2713 if self.upper_bound is None:
2714 self.write_indent(indent + 1, "Upper bound: *")
2715 else:
2716 self.write_indent(indent + 1, f"Upper bound: {self.upper_bound}")
2717 self.write_indent(indent + 1, f"Element type: {self.element_type.name}")
2719 def perform_type_checks(self, mh, value, gstab):
2720 assert isinstance(mh, Message_Handler)
2721 assert isinstance(gstab, Symbol_Table)
2723 if isinstance(value, Array_Aggregate):
2724 return all(
2725 self.element_type.perform_type_checks(mh, v, gstab) for v in value.value
2726 )
2727 else:
2728 assert isinstance(value, Implicit_Null)
2729 return True
2731 def get_example_value(self):
2732 # lobster-exclude: utility method
2733 return "[%s]" % self.element_type.get_example_value()
2736class Union_Type(Type):
2737 # lobster-trace: LRM.union_type
2738 # lobster-trace: LRM.Union_Type_Minimum_Members
2739 # lobster-trace: LRM.Union_Type_Record_Types_Only
2740 """Anonymous union type for record references.
2742 These are declared implicitly when a record component specifies
2743 multiple allowed record types using bracket syntax::
2745 parent [Systemrequirement, Codebeamerrequirement]
2746 ^
2748 :attribute types: the allowed record types
2749 :type: list[Record_Type]
2751 """
2753 def __init__(self, location, types):
2754 assert isinstance(types, list)
2755 assert len(types) >= 1
2756 assert all(isinstance(t, Record_Type) for t in types)
2757 name = "[%s]" % ", ".join(t.name for t in types)
2758 super().__init__(name, location)
2759 self.types = types
2760 self._field_map = None
2762 def get_field_map(self):
2763 # lobster-trace: LRM.Union_Type_Field_Access
2764 """Compute accessible fields across all union members.
2766 Returns a dict mapping field name to a dict with keys:
2768 * ``component``: a representative Composite_Component
2769 * ``n_typ``: the field type (None if conflicting)
2770 * ``count``: how many member types have this field
2771 * ``total``: total number of member types
2772 * ``optional_in_any``: True if optional in at least one member
2774 :rtype: dict[str, dict]
2775 """
2776 if self._field_map is not None:
2777 return self._field_map
2779 field_map = {}
2780 for record_type in self.types:
2781 seen_in_type = set()
2782 for comp in record_type.all_components():
2783 if comp.name in seen_in_type: 2783 ↛ 2784line 2783 didn't jump to line 2784 because the condition on line 2783 was never true
2784 continue
2785 seen_in_type.add(comp.name)
2786 if comp.name not in field_map:
2787 field_map[comp.name] = {
2788 "component": comp,
2789 "n_typ": comp.n_typ,
2790 "count": 1,
2791 "total": len(self.types),
2792 "optional_in_any": comp.optional,
2793 }
2794 else:
2795 info = field_map[comp.name]
2796 info["count"] += 1
2797 # Type identity (is) is correct here:
2798 # non-union type objects are structural
2799 # singletons in the symbol table, so
2800 # identity comparison is both correct and
2801 # cheap.
2802 if info["n_typ"] is not comp.n_typ:
2803 info["n_typ"] = None # type conflict
2804 if comp.optional: 2804 ↛ 2805line 2804 didn't jump to line 2805 because the condition on line 2804 was never true
2805 info["optional_in_any"] = True
2807 self._field_map = field_map
2808 return self._field_map
2810 def dump(self, indent=0): # pragma: no cover
2811 # lobster-exclude: Debugging feature
2812 self.write_indent(indent, "Union_Type")
2813 for t in self.types:
2814 self.write_indent(indent + 1, t.name)
2816 def perform_type_checks(self, mh, value, gstab):
2817 # Union types have no checks of their own; type validation
2818 # happens in Record_Reference.resolve_references() via
2819 # is_compatible(). Returning True unconditionally is
2820 # intentional.
2821 assert isinstance(mh, Message_Handler)
2822 assert isinstance(value, Expression)
2823 assert isinstance(gstab, Symbol_Table)
2824 return True
2826 def is_compatible(self, record_type):
2827 """Test if the given record type is accepted by this union.
2829 :param record_type: type to check
2830 :type record_type: Record_Type
2832 :returns: true if the type is or extends one of the union members
2833 :rtype: bool
2834 """
2835 assert isinstance(record_type, Record_Type)
2836 return any(record_type.is_subclass_of(t) for t in self.types)
2838 def get_example_value(self):
2839 # lobster-exclude: utility method
2840 return "%s_instance" % self.types[0].name
2843class Builtin_Integer(Builtin_Numeric_Type):
2844 # lobster-trace: LRM.Builtin_Types
2845 # lobster-trace: LRM.Integer_Values
2846 """Builtin integer type."""
2848 def __init__(self):
2849 super().__init__("Integer")
2851 def get_example_value(self):
2852 # lobster-exclude: utility method
2853 return "100"
2856class Builtin_Decimal(Builtin_Numeric_Type):
2857 # lobster-trace: LRM.Builtin_Types
2858 # lobster-trace: LRM.Decimal_Values
2859 """Builtin decimal type."""
2861 def __init__(self):
2862 super().__init__("Decimal")
2864 def get_example_value(self):
2865 # lobster-exclude: utility method
2866 return "3.14"
2869class Builtin_Boolean(Builtin_Type):
2870 # lobster-trace: LRM.Builtin_Types
2871 # lobster-trace: LRM.Boolean_Values
2872 """Builtin boolean type."""
2874 def __init__(self):
2875 super().__init__("Boolean")
2877 def get_example_value(self):
2878 # lobster-exclude: utility method
2879 return "true"
2882class Builtin_String(Builtin_Type):
2883 # lobster-trace: LRM.Builtin_Types
2884 # lobster-trace: LRM.String_Values
2885 """Builtin string type."""
2887 def __init__(self):
2888 super().__init__("String")
2890 def get_example_value(self):
2891 # lobster-exclude: utility method
2892 return '"potato"'
2895class Builtin_Markup_String(Builtin_String):
2896 # lobster-trace: LRM.Builtin_Types
2897 # lobster-trace: LRM.Markup_String_Values
2898 """Builtin string type that allows checked references to TRLC
2899 objects.
2900 """
2902 def __init__(self):
2903 super().__init__()
2904 self.name = "Markup_String"
2906 def get_example_value(self):
2907 # lobster-exclude: utility method
2908 return '"also see [[potato]]"'
2911class Package(Entity):
2912 """Packages.
2914 A package is declared when it is first encountered (in either a
2915 rsl or trlc file). A package contains all symbols declared in it,
2916 both types and record objects. A package is not associated with
2917 just a single file, it can be spread over multiple files.
2919 :attribute declared_late: indicates if this package is declared in a \
2920 trlc file
2921 :type: bool
2923 :attribute symbols: symbol table of the package
2924 :type: Symbol_Table
2926 :attribute sub_packages: direct child packages of this package
2927 :type: Symbol_Table
2929 :attribute parent: the parent package, or None for top-level packages
2930 :type: Package
2932 """
2934 def __init__(self, name, location, builtin_stab, declared_late):
2935 # lobster-exclude: Constructor only declares variables
2936 super().__init__(name, location)
2937 assert isinstance(builtin_stab, Symbol_Table)
2938 assert isinstance(declared_late, bool)
2939 self.symbols = Symbol_Table()
2940 self.symbols.make_visible(builtin_stab)
2941 self.declared_late = declared_late
2942 self.sub_packages = Symbol_Table()
2943 self.parent = None
2945 def dump(self, indent=0): # pragma: no cover
2946 # lobster-exclude: Debugging feature
2947 self.write_indent(indent, f"Package {self.name}")
2948 self.write_indent(indent + 1, f"Declared_Late: {self.declared_late}")
2949 self.symbols.dump(indent + 1, omit_heading=True)
2951 def __repr__(self):
2952 return "%s<%s>" % (self.__class__.__name__, self.name)
2955class Composite_Type(Concrete_Type, metaclass=ABCMeta):
2956 """Abstract base for record and tuple types, as they share some
2957 functionality.
2959 :attribute components: type components (including inherited if applicable)
2960 :type: Symbol_Table[Composite_Component]
2962 :attribute description: user-supplied description of the type or None
2963 :type: str
2965 :attribute checks: user-defined checks for this type (excluding \
2966 inherited checks)
2967 :type: list[Check]
2969 """
2971 def __init__(self, name, description, location, package, inherited_symbols=None):
2972 # lobster-trace: LRM.Described_Name_Description
2973 super().__init__(name, location, package)
2974 assert isinstance(description, str) or description is None
2975 assert isinstance(inherited_symbols, Symbol_Table) or inherited_symbols is None
2977 self.components = Symbol_Table(inherited_symbols)
2978 self.description = description
2979 self.checks = []
2981 def add_check(self, n_check):
2982 # lobster-trace: LRM.Check_Evaluation_Order
2983 assert isinstance(n_check, Check)
2984 self.checks.append(n_check)
2986 def iter_checks(self):
2987 # lobster-trace: LRM.Check_Evaluation_Order
2988 yield from self.checks
2990 def all_components(self):
2991 # lobster-exclude: Convenience function
2992 """Convenience function to get a list of all components.
2994 :rtype: list[Composite_Component]
2995 """
2996 return list(self.components.table.values())
2999class Composite_Component(Typed_Entity):
3000 """Component in a record or tuple.
3002 When declaring a composite type, for each component an entity is
3003 declared::
3005 type|tuple T {
3006 foo "blah" optional Boolean
3007 ^1 ^2 ^3 ^4
3009 :attribute description: optional text (see 2) for this component, or None
3010 :type: str
3012 :attribute member_of: a link back to the containing record or tuple; \
3013 for inherited fields this refers back to the original base record type
3014 :type: Composite_Type
3016 :attribute optional: indicates if the component can be null or not (see 3)
3017 :type: bool
3019 """
3021 def __init__(self, name, description, location, member_of, n_typ, optional):
3022 # lobster-trace: LRM.Described_Name_Description
3023 super().__init__(name, location, n_typ)
3024 assert isinstance(description, str) or description is None
3025 assert isinstance(member_of, Composite_Type)
3026 assert isinstance(optional, bool)
3027 self.description = description
3028 self.member_of = member_of
3029 self.optional = optional
3031 def dump(self, indent=0): # pragma: no cover
3032 # lobster-exclude: Debugging feature
3033 self.write_indent(indent, f"Composite_Component {self.name}")
3034 if self.description:
3035 self.write_indent(indent + 1, f"Description: {self.description}")
3036 self.write_indent(indent + 1, f"Optional: {self.optional}")
3037 self.write_indent(indent + 1, f"Type: {self.n_typ.name}")
3039 def __repr__(self):
3040 return "%s<%s>" % (
3041 self.__class__.__name__,
3042 self.member_of.fully_qualified_name() + "." + self.name,
3043 )
3046class Record_Type(Composite_Type):
3047 """A user-defined record type.
3049 In this example::
3051 type T "optional description of T" extends Root_T {
3052 ^1 ^2 ^3
3054 Note that (1) is part of the :class:`Entity` base, and (2) is part
3055 of the :class:`Composite_Type` base.
3057 :attribute parent: root type or None, indicated by (3) above
3058 :type: Record_Type
3060 :attribute frozen: mapping of frozen components
3061 :type: dict[str, Expression]
3063 :attribute is_final: type is final (i.e. no new components may be declared)
3064 :type: bool
3066 :attribute is_abstract: type is abstract
3067 :type: bool
3069 """
3071 def __init__(self, name, description, location, package, n_parent, is_abstract):
3072 # lobster-exclude: Constructor only declares variables
3073 assert isinstance(n_parent, Record_Type) or n_parent is None
3074 assert isinstance(is_abstract, bool)
3075 super().__init__(
3076 name,
3077 description,
3078 location,
3079 package,
3080 n_parent.components if n_parent else None,
3081 )
3082 self.parent = n_parent
3083 self.frozen = {}
3084 self.is_final = n_parent.is_final if n_parent else False
3085 self.is_abstract = is_abstract
3087 def iter_checks(self):
3088 # lobster-trace: LRM.Check_Evaluation_Order
3089 # lobster-trace: LRM.Check_Evaluation_Order_For_Extensions
3090 if self.parent:
3091 yield from self.parent.iter_checks()
3092 yield from self.checks
3094 def dump(self, indent=0): # pragma: no cover
3095 # lobster-exclude: Debugging feature
3096 self.write_indent(indent, f"Record_Type {self.name}")
3097 if self.description:
3098 self.write_indent(indent + 1, f"Description: {self.description}")
3099 if self.parent:
3100 self.write_indent(indent + 1, f"Parent: {self.parent.name}")
3101 self.components.dump(indent + 1, omit_heading=True)
3102 if self.checks:
3103 self.write_indent(indent + 1, "Checks")
3104 for n_check in self.checks:
3105 n_check.dump(indent + 2)
3106 else:
3107 self.write_indent(indent + 1, "Checks: None")
3109 def all_components(self):
3110 """Convenience function to get a list of all components.
3112 :rtype: list[Composite_Component]
3113 """
3114 if self.parent:
3115 return self.parent.all_components() + list(self.components.table.values())
3116 else:
3117 return list(self.components.table.values())
3119 def is_subclass_of(self, record_type):
3120 """Checks if this record type is or inherits from the given type
3122 :param record_type: check if are or extend this type
3123 :type record_type: Record_Type
3125 :returns: true if we are or extend the given type
3126 :rtype: Boolean
3127 """
3128 assert isinstance(record_type, Record_Type)
3130 ptr = self
3131 while ptr:
3132 if ptr is record_type:
3133 return True
3134 else:
3135 ptr = ptr.parent
3136 return False
3138 def is_frozen(self, n_component):
3139 """Test if the given component is frozen.
3141 :param n_component: a composite component of this record type \
3142 (or any of its parents)
3143 :type n_component: Composite_Component
3145 :rtype: bool
3146 """
3147 assert isinstance(n_component, Composite_Component)
3148 if n_component.name in self.frozen:
3149 return True
3150 elif self.parent:
3151 return self.parent.is_frozen(n_component)
3152 else:
3153 return False
3155 def get_freezing_expression(self, n_component):
3156 """Retrieve the frozen value for a frozen component
3158 It is an internal compiler error to call this method with a
3159 component that his not frozen.
3161 :param n_component: a frozen component of this record type \
3162 (or any of its parents)
3163 :type n_component: Composite_Component
3165 :rtype: Expression
3167 """
3168 assert isinstance(n_component, Composite_Component)
3169 if n_component.name in self.frozen: 3169 ↛ 3171line 3169 didn't jump to line 3171 because the condition on line 3169 was always true
3170 return self.frozen[n_component.name]
3171 elif self.parent:
3172 return self.parent.get_freezing_expression(n_component)
3173 else:
3174 assert False
3176 def get_example_value(self):
3177 # lobster-exclude: utility method
3178 return "%s_instance" % self.name
3181class Tuple_Type(Composite_Type):
3182 """A user-defined tuple type.
3184 In this example::
3186 tuple T "optional description of T" {
3187 ^1 ^2
3189 Note that (1) is part of the :class:`Entity` base, and (2) is part
3190 of the :class:`Composite_Type` base.
3192 :attribute separators: list of syntactic separators.
3193 :type: list[Separator]
3195 Note the list of separators will either be empty, or there will be
3196 precisely one less separator than components.
3198 """
3200 def __init__(self, name, description, location, package):
3201 # lobster-trace: LRM.Tuple_Declaration
3202 super().__init__(name, description, location, package)
3203 self.separators = []
3205 def add_separator(self, n_separator):
3206 # lobster-exclude: utility method
3207 assert isinstance(n_separator, Separator)
3208 assert len(self.separators) + 1 == len(self.components.table)
3209 self.separators.append(n_separator)
3211 def iter_separators(self):
3212 """Iterate over all separators"""
3213 # lobster-exclude: utility method
3214 yield from self.separators
3216 def iter_sequence(self):
3217 """Iterate over all components and separators in syntactic order"""
3218 # lobster-exclude: utility method
3219 if self.separators:
3220 for i, n_component in enumerate(self.components.table.values()):
3221 yield n_component
3222 if i < len(self.separators):
3223 yield self.separators[i]
3224 else:
3225 yield from self.components.table.values()
3227 def has_separators(self):
3228 """Returns true if a tuple type requires separators"""
3229 # lobster-exclude: utility method
3230 return bool(self.separators)
3232 def dump(self, indent=0): # pragma: no cover
3233 # lobster-exclude: Debugging feature
3234 self.write_indent(indent, f"Tuple_Type {self.name}")
3235 if self.description:
3236 self.write_indent(indent + 1, f"Description: {self.description}")
3237 self.write_indent(indent + 1, "Fields")
3238 for n_item in self.iter_sequence():
3239 n_item.dump(indent + 2)
3240 if self.checks:
3241 self.write_indent(indent + 1, "Checks")
3242 for n_check in self.checks:
3243 n_check.dump(indent + 2)
3244 else:
3245 self.write_indent(indent + 1, "Checks: None")
3247 def perform_type_checks(self, mh, value, gstab):
3248 # lobster-trace: LRM.Check_Evaluation_Order
3249 assert isinstance(mh, Message_Handler)
3250 assert isinstance(gstab, Symbol_Table)
3252 if isinstance(value, Tuple_Aggregate): 3252 ↛ 3259line 3252 didn't jump to line 3259 because the condition on line 3252 was always true
3253 ok = True
3254 for check in self.iter_checks():
3255 if not check.perform(mh, value, gstab):
3256 ok = False
3257 return ok
3258 else:
3259 assert isinstance(value, Implicit_Null)
3260 return True
3262 def get_example_value(self):
3263 # lobster-exclude: utility method
3264 parts = []
3265 for n_item in self.iter_sequence():
3266 if isinstance(n_item, Composite_Component):
3267 parts.append(n_item.n_typ.get_example_value())
3268 else:
3269 parts.append(n_item.to_string())
3270 if self.has_separators():
3271 return " ".join(parts)
3272 else:
3273 return "(%s)" % ", ".join(parts)
3276class Separator(Node):
3277 # lobster-trace: LRM.Tuple_Declaration
3278 """User-defined syntactic separator
3280 For example::
3282 separator x
3283 ^1
3285 :attribute token: token used to separate fields of the tuple
3286 :type: Token
3287 """
3289 def __init__(self, token):
3290 super().__init__(token.location)
3291 assert isinstance(token, Token) and token.kind in (
3292 "IDENTIFIER",
3293 "AT",
3294 "COLON",
3295 "SEMICOLON",
3296 )
3297 self.token = token
3299 def to_string(self):
3300 return {"AT": "@", "COLON": ":", "SEMICOLON": ";"}.get(
3301 self.token.kind, self.token.value
3302 )
3304 def dump(self, indent=0): # pragma: no cover
3305 self.write_indent(indent, f"Separator {self.token.value}")
3308class Enumeration_Type(Concrete_Type):
3309 """User-defined enumeration types.
3311 For example::
3313 enum T "potato" {
3314 ^1 ^2
3316 :attribute description: user supplied optional description, or None
3317 :type: str
3319 :attribute literals: the literals in this enumeration
3320 :type: Symbol_Table[Enumeration_Literal_Spec]
3322 """
3324 def __init__(self, name, description, location, package):
3325 # lobster-trace: LRM.Described_Name_Description
3326 super().__init__(name, location, package)
3327 assert isinstance(description, str) or description is None
3328 self.literals = Symbol_Table()
3329 self.description = description
3331 def dump(self, indent=0): # pragma: no cover
3332 # lobster-exclude: Debugging feature
3333 self.write_indent(indent, f"Enumeration_Type {self.name}")
3334 if self.description:
3335 self.write_indent(indent + 1, f"Description: {self.description}")
3336 self.literals.dump(indent + 1, omit_heading=True)
3338 def get_example_value(self):
3339 # lobster-exclude: utility method
3340 options = list(self.literals.values())
3341 if options:
3342 choice = len(options) // 2
3343 return self.name + "." + choice.name
3344 else:
3345 return "ERROR"
3348class Enumeration_Literal_Spec(Typed_Entity):
3349 """Declared literal in an enumeration declaration.
3351 Note that for literals mentioned later in record object
3352 declarations, we use :class:`Enumeration_Literal`. Literal specs
3353 are used here::
3355 enum ASIL {
3356 QM "not safety related"
3357 ^1 ^2
3359 :attribute description: the optional user-supplied description, or None
3360 :type: str
3362 """
3364 def __init__(self, name, description, location, enum):
3365 # lobster-trace: LRM.Described_Name_Description
3366 super().__init__(name, location, enum)
3367 assert isinstance(description, str) or description is None
3368 assert isinstance(enum, Enumeration_Type)
3369 self.description = description
3371 def dump(self, indent=0): # pragma: no cover
3372 # lobster-exclude: Debugging feature
3373 self.write_indent(indent, f"Enumeration_Literal_Spec {self.name}")
3374 if self.description:
3375 self.write_indent(indent + 1, f"Description: {self.description}")
3378class Record_Object(Typed_Entity):
3379 """A declared instance of a record type.
3381 This is going to be the bulk of all entities created by TRLC::
3383 section "Potato" {
3384 ^5
3385 Requirement PotatoReq {
3386 ^1 ^2
3387 component1 = 42
3388 ^3 ^4
3390 Note that the name (see 2) and type (see 1) of the object is
3391 provided by the name attribute of the :class:`Typed_Entity` base
3392 class.
3394 :attribute field: the specific values for all components (see 3 and 4)
3395 :type: dict[str, Expression]
3397 :attribute section: None or the section this record is contained in (see 5)
3398 :type: Section
3400 :attribute n_package: The package in which this record is declared in
3401 :type: Section
3403 The actual type of expressions in the field attribute are limited
3404 to:
3406 * :class:`Literal`
3407 * :class:`Unary_Expression`
3408 * :class:`Array_Aggregate`
3409 * :class:`Tuple_Aggregate`
3410 * :class:`Record_Reference`
3411 * :class:`Implicit_Null`
3413 """
3415 def __init__(self, name, location, n_typ, section, n_package):
3416 # lobster-trace: LRM.Section_Declaration
3417 # lobster-trace: LRM.Unspecified_Optional_Components
3418 # lobster-trace: LRM.Record_Object_Declaration
3420 assert isinstance(n_typ, Record_Type)
3421 assert isinstance(section, list) or section is None
3422 assert isinstance(n_package, Package)
3423 super().__init__(name, location, n_typ)
3424 self.field = {
3425 comp.name: Implicit_Null(self, comp) for comp in self.n_typ.all_components()
3426 }
3427 self.section = section
3428 self.n_package = n_package
3430 def fully_qualified_name(self):
3431 """Return the FQN for this type (i.e. PACKAGE.NAME)
3433 :returns: the object's full name
3434 :rtype: str
3435 """
3436 return self.n_package.name + "." + self.name
3438 def to_python_dict(self):
3439 """Return an evaluated and simplified object for Python.
3441 For example it might provide::
3443 {"foo" : [1, 2, 3],
3444 "bar" : None,
3445 "baz" : "value"}
3447 This is a function especially designed for the Python API. The
3448 name of the object itself is not in this returned dictionary.
3450 """
3451 return {name: value.to_python_object() for name, value in self.field.items()}
3453 def is_component_implicit_null(self, component) -> bool:
3454 return not isinstance(self.field[component.name], Implicit_Null)
3456 def assign(self, component, value):
3457 assert isinstance(component, Composite_Component)
3458 assert isinstance(
3459 value,
3460 (
3461 Literal,
3462 Array_Aggregate,
3463 Tuple_Aggregate,
3464 Record_Reference,
3465 Implicit_Null,
3466 Unary_Expression,
3467 ),
3468 ), "value is %s" % value.__class__.__name__
3469 if self.is_component_implicit_null(component): 3469 ↛ 3470line 3469 didn't jump to line 3470 because the condition on line 3469 was never true
3470 raise KeyError(
3471 f"Component {component.name} already \
3472 assigned to {self.n_typ.name} {self.name}!"
3473 )
3474 self.field[component.name] = value
3476 def dump(self, indent=0): # pragma: no cover
3477 # lobster-exclude: Debugging feature
3478 self.write_indent(indent, f"Record_Object {self.name}")
3479 self.write_indent(indent + 1, f"Type: {self.n_typ.name}")
3480 for key, value in self.field.items():
3481 self.write_indent(indent + 1, f"Field {key}")
3482 value.dump(indent + 2)
3483 if self.section:
3484 self.section[-1].dump(indent + 1)
3486 def resolve_references(self, mh):
3487 assert isinstance(mh, Message_Handler)
3488 for val in self.field.values():
3489 val.resolve_references(mh)
3491 def perform_checks(self, mh, gstab):
3492 # lobster-trace: LRM.Check_Evaluation_Order
3493 # lobster-trace: LRM.Evaluation_Of_Checks
3494 assert isinstance(mh, Message_Handler)
3495 assert isinstance(gstab, Symbol_Table)
3497 ok = True
3499 # First evaluate all tuple checks
3500 for n_comp in self.n_typ.all_components():
3501 if not n_comp.n_typ.perform_type_checks(mh, self.field[n_comp.name], gstab):
3502 ok = False
3504 # TODO: Is there a bug here (a check relies on a tuple check)?
3506 # Then evaluate all record checks
3507 for check in self.n_typ.iter_checks():
3508 # Prints messages, if applicable. Raises exception on
3509 # fatal checks, which causes this to abort.
3510 if not check.perform(mh, self, gstab):
3511 ok = False
3513 return ok
3515 def __repr__(self):
3516 return "%s<%s>" % (
3517 self.__class__.__name__,
3518 self.n_package.name + "." + self.n_typ.name + "." + self.name,
3519 )
3522class Section(Entity):
3523 # lobster-trace: LRM.Section_Declaration
3524 """A section for readability
3526 This represents a section construct in TRLC files to group record
3527 objects together::
3529 section "Foo" {
3530 ^^^^^ parent section
3531 section "Bar" {
3532 ^^^^^ section
3534 :attribute parent: the parent section or None
3535 :type: Section
3537 """
3539 def __init__(self, name, location, parent):
3540 super().__init__(name, location)
3541 assert isinstance(parent, Section) or parent is None
3542 self.parent = parent
3544 def dump(self, indent=0): # pragma: no cover
3545 self.write_indent(indent, f"Section {self.name}")
3546 if self.parent is None:
3547 self.write_indent(indent + 1, "Parent: None")
3548 else:
3549 self.write_indent(indent + 1, f"Parent: {self.parent.name}")
3552##############################################################################
3553# Symbol Table & Scopes
3554##############################################################################
3557class Symbol_Table:
3558 """Symbol table mapping names to entities"""
3560 def __init__(self, parent=None):
3561 # lobster-exclude: Constructor only declares variables
3562 assert isinstance(parent, Symbol_Table) or parent is None
3563 self.parent = parent
3564 self.imported = []
3565 self.table = OrderedDict()
3566 self.trlc_files = []
3567 self.section_names = []
3569 @staticmethod
3570 def simplified_name(name):
3571 # lobster-trace: LRM.Sufficiently_Distinct
3572 assert isinstance(name, str)
3573 return name.lower().replace("_", "")
3575 def all_names(self):
3576 # lobster-exclude: API for users
3577 """All names in the symbol table
3579 :rtype: set[str]
3580 """
3581 rv = set(item.name for item in self.table.values())
3582 if self.parent:
3583 rv |= self.parent.all_names()
3584 return rv
3586 def iter_record_objects_by_section(self):
3587 """API for users
3589 Retriving information about the section hierarchy for record objects
3590 Inputs: folder with trlc files where trlc files have sections,
3591 sub sections and record objects
3592 Output: Information about sections and level of sections,
3593 record objects and levels of record object
3594 """
3595 for record_object in self.iter_record_objects():
3596 location = record_object.location.file_name
3597 if location not in self.trlc_files: 3597 ↛ 3600line 3597 didn't jump to line 3600 because the condition on line 3597 was always true
3598 self.trlc_files.append(location)
3599 yield location
3600 if record_object.section:
3601 object_level = len(record_object.section) - 1
3602 for level, section in enumerate(record_object.section):
3603 if section not in self.section_names: 3603 ↛ 3602line 3603 didn't jump to line 3602 because the condition on line 3603 was always true
3604 self.section_names.append(section)
3605 yield section.name, level
3606 yield record_object, object_level
3607 else:
3608 object_level = 0
3609 yield record_object, object_level
3611 def iter_record_objects(self):
3612 # lobster-exclude: API for users
3613 """Iterate over all record objects
3615 :rtype: iterable[Record_Object]
3616 """
3617 for item in self.table.values():
3618 if isinstance(item, Package):
3619 yield from item.symbols.iter_record_objects()
3621 elif isinstance(item, Record_Object):
3622 yield item
3624 def values(self, subtype=None):
3625 # lobster-exclude: API for users
3626 assert subtype is None or isinstance(subtype, type)
3627 if self.parent:
3628 yield from self.parent.values(subtype)
3629 for name in sorted(self.table):
3630 if subtype is None or isinstance(self.table[name], subtype):
3631 yield self.table[name]
3633 def make_visible(self, stab):
3634 assert isinstance(stab, Symbol_Table)
3635 self.imported.append(stab)
3637 def register(self, mh, entity):
3638 # lobster-trace: LRM.Duplicate_Types
3639 # lobster-trace: LRM.Unique_Enumeration_Literals
3640 # lobster-trace: LRM.Tuple_Unique_Field_Names
3641 # lobster-trace: LRM.Sufficiently_Distinct
3642 # lobster-trace: LRM.Unique_Object_Names
3644 assert isinstance(mh, Message_Handler)
3645 assert isinstance(entity, Entity)
3647 simple_name = self.simplified_name(entity.name)
3649 if self.contains_raw(simple_name):
3650 pdef = self.lookup_direct(mh, entity.name, entity.location, simplified=True)
3651 if pdef.name == entity.name:
3652 mh.error(
3653 entity.location,
3654 "duplicate definition, previous definition at %s"
3655 % mh.cross_file_reference(pdef.location),
3656 )
3657 else:
3658 mh.error(
3659 entity.location,
3660 "%s is too similar to %s, declared at %s"
3661 % (entity.name, pdef.name, mh.cross_file_reference(pdef.location)),
3662 )
3664 else:
3665 self.table[simple_name] = entity
3667 def register_with_key(self, mh, entity, key_name):
3668 """Register an entity under a different key name (leaf name).
3670 Used to register nested packages in their parent's ``sub_packages``
3671 table keyed by their leaf segment rather than their full name.
3673 :param mh: The message handler to use
3674 :type mh: Message_Handler
3676 :param entity: the entity to register
3677 :type entity: Entity
3679 :param key_name: the name to use as the lookup key
3680 :type key_name: str
3681 """
3682 # lobster-trace: LRM.Sufficiently_Distinct
3683 assert isinstance(mh, Message_Handler)
3684 assert isinstance(entity, Entity)
3685 assert isinstance(key_name, str)
3687 simple_key = self.simplified_name(key_name)
3689 if simple_key in self.table:
3690 existing = self.table[simple_key]
3691 if existing.name == entity.name: 3691 ↛ 3700line 3691 didn't jump to line 3700 because the condition on line 3691 was always true
3692 mh.error(
3693 entity.location,
3694 "duplicate definition, previous definition at %s"
3695 % mh.cross_file_reference(existing.location),
3696 explanation="rename or remove one of the two "
3697 "declarations so each sub-package has a unique name",
3698 )
3699 else:
3700 mh.error(
3701 entity.location,
3702 "%s is too similar to %s, declared at %s"
3703 % (
3704 entity.name,
3705 existing.name,
3706 mh.cross_file_reference(existing.location),
3707 ),
3708 explanation="package names must be sufficiently "
3709 "distinct (case and underscores are ignored); "
3710 "rename one of the two packages",
3711 )
3712 else:
3713 self.table[simple_key] = entity
3715 def lookup_sub_package(self, segment):
3716 """Look up a direct child package by its leaf segment name.
3718 Used for traversing the nested package hierarchy, where the table
3719 key is the simplified leaf segment but the entity name is the full
3720 dotted package name.
3722 :param segment: the leaf name to look for (e.g. ``"bar"`` for \
3723 ``foo.bar``)
3724 :type segment: str
3726 :returns: the child Package, or None if not found
3727 :rtype: Package or None
3728 """
3729 assert isinstance(segment, str)
3730 simple_key = self.simplified_name(segment)
3731 if simple_key in self.table:
3732 entity = self.table[simple_key]
3733 if isinstance(entity, Package): 3733 ↛ 3735line 3733 didn't jump to line 3735 because the condition on line 3733 was always true
3734 return entity
3735 return None
3737 def __contains__(self, name):
3738 # lobster-trace: LRM.Described_Name_Equality
3739 return self.contains(name)
3741 def contains_raw(self, simple_name, precise_name=None):
3742 # lobster-trace: LRM.Described_Name_Equality
3743 # lobster-trace: LRM.Sufficiently_Distinct
3744 #
3745 # Internal function to test if the simplified name is in the
3746 # table.
3747 assert isinstance(simple_name, str)
3748 assert isinstance(precise_name, str) or precise_name is None
3750 if simple_name in self.table:
3751 # No need to continue searching since registering a
3752 # clashing name would have been stopped
3753 return precise_name is None or self.table[simple_name].name == precise_name
3755 elif self.parent:
3756 return self.parent.contains_raw(simple_name, precise_name)
3758 for stab in self.imported:
3759 if stab.contains_raw(simple_name, precise_name):
3760 return True
3762 return False
3764 def contains(self, name):
3765 # lobster-trace: LRM.Described_Name_Equality
3766 """Tests if the given name is in the table
3768 :param name: the name to test
3769 :type name: str
3771 :rtype: bool
3772 """
3773 assert isinstance(name, str)
3774 return self.contains_raw(self.simplified_name(name), name)
3776 def lookup_assuming(self, mh, name, required_subclass=None):
3777 # lobster-trace: LRM.Described_Name_Equality
3778 # lobster-trace: LRM.Sufficiently_Distinct
3779 """Retrieve an object from the table assuming its there
3781 This is intended for the API specifically where you want to
3782 e.g. find some user-defined types you know are there.
3784 :param mh: The message handler to use
3785 :type mh: Message_Handler
3787 :param name: The name to search for
3788 :type name: str
3790 :param required_subclass: If set, creates an error if the object \
3791 is not an instance of the given class
3792 :type required_subclass: type
3794 :raise TRLC_Error: if the object is not of the required subclass
3795 :returns: the specified entity (or None if it does not exist)
3796 :rtype: Entity
3798 """
3799 assert isinstance(mh, Message_Handler)
3800 assert isinstance(name, str)
3801 assert isinstance(required_subclass, type) or required_subclass is None
3803 simple_name = self.simplified_name(name)
3805 ptr = self
3806 for ptr in [self] + self.imported: 3806 ↛ 3825line 3806 didn't jump to line 3825 because the loop on line 3806 didn't complete
3807 while ptr: 3807 ↛ 3806line 3807 didn't jump to line 3806 because the condition on line 3807 was always true
3808 if simple_name in ptr.table: 3808 ↛ 3823line 3808 didn't jump to line 3823 because the condition on line 3808 was always true
3809 rv = ptr.table[simple_name]
3810 if rv.name != name: 3810 ↛ 3811line 3810 didn't jump to line 3811 because the condition on line 3810 was never true
3811 return None
3813 if required_subclass is not None and not isinstance( 3813 ↛ 3816line 3813 didn't jump to line 3816 because the condition on line 3813 was never true
3814 rv, required_subclass
3815 ):
3816 mh.error(
3817 rv.location,
3818 "%s %s is not a %s"
3819 % (rv.__class__.__name__, name, required_subclass.__name__),
3820 )
3821 return rv
3822 else:
3823 ptr = ptr.parent
3825 return None
3827 def lookup_direct(
3828 self, mh, name, error_location, required_subclass=None, simplified=False
3829 ):
3830 # lobster-trace: LRM.Described_Name_Equality
3831 # lobster-trace: LRM.Sufficiently_Distinct
3832 # lobster-trace: LRM.Valid_Base_Names
3833 # lobster-trace: LRM.Valid_Access_Prefixes
3834 # lobster-trace: LRM.Valid_Function_Prefixes
3835 """Retrieve an object from the table
3837 For example::
3839 pkg = stab.lookup_direct(mh,
3840 "potato",
3841 Location("foobar.txt", 42),
3842 Package)
3844 This would search for an object named ``potato``. If it is
3845 found, and it is a package, it is returned. If it is not a
3846 Package, then the following error is issued::
3848 foobar.txt:42: error: Enumeration_Type potato is not a Package
3850 If it is not found at all, then the following error is issued::
3852 foobar.txt:42: error: unknown symbol potato
3854 :param mh: The message handler to use
3855 :type mh: Message_Handler
3857 :param name: The name to search for
3858 :type name: str
3860 :param error_location: Where to create the error if the name is \
3861 not found
3862 :type error_location: Location
3864 :param required_subclass: If set, creates an error if the object \
3865 is not an instance of the given class
3866 :type required_subclass: type
3868 :param simplified: If set, look up the given simplified name instead \
3869 of the actual name
3870 :type simplified: bool
3872 :raise TRLC_Error: if the name is not in the table
3873 :raise TRLC_Error: if the object is not of the required subclass
3874 :returns: the specified entity
3875 :rtype: Entity
3877 """
3878 assert isinstance(mh, Message_Handler)
3879 assert isinstance(name, str)
3880 assert isinstance(error_location, Location)
3881 assert isinstance(required_subclass, type) or required_subclass is None
3882 assert isinstance(simplified, bool)
3884 simple_name = self.simplified_name(name)
3885 ptr = self
3886 options = []
3888 for ptr in [self] + self.imported:
3889 while ptr:
3890 if simple_name in ptr.table:
3891 rv = ptr.table[simple_name]
3892 if not simplified and rv.name != name: 3892 ↛ 3893line 3892 didn't jump to line 3893 because the condition on line 3892 was never true
3893 mh.error(
3894 error_location,
3895 "unknown symbol %s, did you mean %s?" % (name, rv.name),
3896 )
3898 if required_subclass is not None and not isinstance(
3899 rv, required_subclass
3900 ):
3901 mh.error(
3902 error_location,
3903 "%s %s is not a %s"
3904 % (rv.__class__.__name__, name, required_subclass.__name__),
3905 )
3906 return rv
3907 else:
3908 options += list(item.name for item in ptr.table.values())
3909 ptr = ptr.parent
3911 matches = get_close_matches(word=name, possibilities=options, n=1)
3913 if matches:
3914 mh.error(
3915 error_location,
3916 "unknown symbol %s, did you mean %s?" % (name, matches[0]),
3917 )
3918 else:
3919 mh.error(error_location, "unknown symbol %s" % name)
3921 def lookup(self, mh, referencing_token, required_subclass=None):
3922 # lobster-trace: LRM.Described_Name_Equality
3923 assert isinstance(mh, Message_Handler)
3924 assert isinstance(referencing_token, Token)
3925 assert referencing_token.kind in ("IDENTIFIER", "BUILTIN")
3926 assert isinstance(required_subclass, type) or required_subclass is None
3928 return self.lookup_direct(
3929 mh=mh,
3930 name=referencing_token.value,
3931 error_location=referencing_token.location,
3932 required_subclass=required_subclass,
3933 )
3935 def write_indent(self, indent, message): # pragma: no cover
3936 # lobster-exclude: Debugging feature
3937 assert isinstance(indent, int)
3938 assert indent >= 0
3939 assert isinstance(message, str)
3940 print(" " * (3 * indent) + message)
3942 def dump(self, indent=0, omit_heading=False): # pragma: no cover
3943 # lobster-exclude: Debugging feature
3944 if omit_heading:
3945 new_indent = indent
3946 else:
3947 self.write_indent(indent, "Symbol_Table")
3948 new_indent = indent + 1
3949 ptr = self
3950 while ptr:
3951 for name in ptr.table:
3952 ptr.table[name].dump(new_indent)
3953 ptr = ptr.parent
3955 @classmethod
3956 def create_global_table(cls, mh):
3957 # lobster-trace: LRM.Builtin_Types
3958 # lobster-trace: LRM.Builtin_Functions
3959 # lobster-trace: LRM.Builtin_Type_Conversion_Functions
3960 # lobster-trace: LRM.Signature_Len
3961 # lobster-trace: LRM.Signature_String_End_Functions
3962 # lobster-trace: LRM.Signature_Matches
3964 stab = Symbol_Table()
3965 stab.register(mh, Builtin_Integer())
3966 stab.register(mh, Builtin_Decimal())
3967 stab.register(mh, Builtin_Boolean())
3968 stab.register(mh, Builtin_String())
3969 stab.register(mh, Builtin_Markup_String())
3970 stab.register(mh, Builtin_Function("len", 1))
3971 stab.register(mh, Builtin_Function("startswith", 2))
3972 stab.register(mh, Builtin_Function("endswith", 2))
3973 stab.register(mh, Builtin_Function("matches", 2))
3974 stab.register(mh, Builtin_Function("oneof", 1, arity_at_least=True))
3976 return stab
3979class Scope:
3980 def __init__(self):
3981 # lobster-exclude: Constructor only declares variables
3982 self.scope = []
3984 def push(self, stab):
3985 assert isinstance(stab, Symbol_Table)
3986 self.scope.append(stab)
3988 def pop(self):
3989 self.scope.pop()
3991 def contains(self, name):
3992 assert isinstance(name, str)
3994 for stab in reversed(self.scope):
3995 if stab.contains(name):
3996 return True
3997 return False
3999 def lookup(self, mh, referencing_token, required_subclass=None):
4000 assert len(self.scope) >= 1
4001 assert isinstance(mh, Message_Handler)
4002 assert isinstance(referencing_token, Token)
4003 assert referencing_token.kind in ("IDENTIFIER", "BUILTIN")
4004 assert isinstance(required_subclass, type) or required_subclass is None
4006 for stab in reversed(self.scope[1:]):
4007 if stab.contains(referencing_token.value):
4008 return stab.lookup(mh, referencing_token, required_subclass)
4009 return self.scope[0].lookup(mh, referencing_token, required_subclass)
4011 def size(self):
4012 return len(self.scope)