Coverage for trlc/ast.py: 90%
1317 statements
« prev ^ index » next coverage.py v7.10.7, created at 2026-09-04 11:46 +0000
« prev ^ index » next coverage.py v7.10.7, created at 2026-09-04 11:46 +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: package imported by this file
223 :type: list[Package]
225 :attribute items: list of
226 :type: list[Node]
228 """
230 def __init__(self, file_name):
231 # lobster-exclude: Constructor only declares variables
232 super().__init__(Location(file_name))
233 self.package = None
234 self.imports = None
235 self.raw_imports = []
236 self.items = []
238 def dump(self, indent=0): # pragma: no cover
239 # lobster-exclude: Debugging feature
240 self.write_indent(indent, f"Compilation_Unit ({self.location.file_name})")
241 for t_import in self.raw_imports:
242 self.write_indent(indent + 1, f"Import: {t_import.value}")
243 for n_item in self.items:
244 n_item.dump(indent + 1)
246 def set_package(self, pkg):
247 # lobster-trace: LRM.Current_Package
248 assert isinstance(pkg, Package)
249 self.package = pkg
251 def add_import(self, mh, t_import):
252 # lobster-trace: LRM.Import_Visibility
253 # lobster-trace: LRM.Self_Imports
254 assert isinstance(mh, Message_Handler)
255 assert isinstance(t_import, Token)
256 assert t_import.kind == "IDENTIFIER"
258 if t_import.value == self.package.name:
259 mh.error(
260 t_import.location, "package %s cannot import itself" % self.package.name
261 )
263 # Skip duplicates
264 for t_previous in self.raw_imports:
265 if t_previous.value == t_import.value:
266 mh.warning(
267 t_import.location, "duplicate import of package %s" % t_import.value
268 )
269 return
271 self.raw_imports.append(t_import)
273 def resolve_imports(self, mh, stab):
274 # lobster-trace: LRM.Import_Visibility
275 assert isinstance(mh, Message_Handler)
276 assert isinstance(stab, Symbol_Table)
277 self.imports = set()
278 for t_import in self.raw_imports:
279 # We can ignore errors here, because that just means we
280 # generate more error later.
281 try:
282 a_import = stab.lookup(mh, t_import, Package)
283 self.imports.add(a_import)
284 a_import.set_ast_link(t_import)
285 except TRLC_Error:
286 pass
288 def is_visible(self, n_pkg):
289 # lobster-trace: LRM.Import_Visibility
290 assert self.imports is not None
291 assert isinstance(n_pkg, Package)
292 return n_pkg == self.package or n_pkg in self.imports
294 def add_item(self, node):
295 # lobster-trace: LRM.RSL_File
296 # lobster-trace: LRM.TRLC_File
297 assert isinstance(node, (Concrete_Type, Check_Block, Record_Object)), (
298 "trying to add %s to a compilation unit" % node.__class__.__name__
299 )
300 self.items.append(node)
303class Check(Node):
304 """User defined check
306 This represent a single user-defined check inside a check block::
308 checks T {
309 a /= null implies a > 5, warning "potato", a
310 ^^^^^^^^^^^^^^^^^^^^^^^1 ^2 ^3 ^4
312 :attribute n_type: The tuple/record type this check applies to
313 :type: Composite_Type
315 :attribute n_expr: The boolean expression for the check (see 1)
316 :type: Expression
318 :attribute n_anchor: The (optional) record component where the message \
319 should be issued (or None) (see 4)
320 :type: Composite_Component
322 :attribute severity: warning, error, or fatal (see 2; also if this is \
323 not specified the default is 'error')
324 :type: str
326 :attribute message: the user-supplied message (see 3)
327 :type: str
328 """
330 def __init__(self, n_type, n_expr, n_anchor, severity, t_message, extrainfo):
331 # lobster-trace: LRM.Check_Block
332 assert isinstance(n_type, Composite_Type)
333 assert isinstance(n_expr, Expression)
334 assert isinstance(n_anchor, Composite_Component) or n_anchor is None
335 assert severity in ("warning", "error", "fatal")
336 assert isinstance(t_message, Token)
337 assert t_message.kind == "STRING"
338 assert isinstance(extrainfo, str) or extrainfo is None
339 super().__init__(t_message.location)
341 self.n_type = n_type
342 self.n_expr = n_expr
343 self.n_anchor = n_anchor
344 self.severity = severity
345 # lobster-trace: LRM.No_Newlines_In_Message
346 # This is the error recovery strategy if we find newlines in
347 # the short error messages: we just remove them. The error
348 # raised is non-fatal.
349 self.message = t_message.value.replace("\n", " ")
350 self.extrainfo = extrainfo
351 self._uses_field_access = None
353 @property
354 def uses_field_access(self):
355 """Cached test: does this check's expression follow a record/union
356 reference?
358 Returns True if any sub-expression of the check expression is a
359 :class:`Field_Access_Expression` whose prefix has a
360 :class:`Record_Type` or :class:`Union_Type` type. Used by
361 the VCG to split checks into Phase A ("at declaration") and
362 Phase B ("after references").
364 :return: whether this check dereferences a record/union reference
365 :rtype: bool
367 """
368 if self._uses_field_access is None:
369 self._uses_field_access = self.n_expr.uses_field_access()
370 return self._uses_field_access
372 def dump(self, indent=0): # pragma: no cover
373 # lobster-exclude: Debugging feature
374 if self.severity == "warning":
375 self.write_indent(indent, f"Warning '{self.message}'")
376 elif self.severity == "error":
377 self.write_indent(indent, f"Error '{self.message}'")
378 else:
379 self.write_indent(indent, f"Fatal error '{self.message}'")
380 if self.n_anchor:
381 self.write_indent(indent + 1, f"Anchor: {self.n_anchor.name}")
382 self.n_expr.dump(indent + 1)
384 def get_real_location(self, composite_object):
385 # lobster-exclude: LRM.Anchoring
386 assert isinstance(composite_object, (Record_Object, Tuple_Aggregate))
387 if isinstance(composite_object, Record_Object):
388 fields = composite_object.field
389 else:
390 fields = composite_object.value
392 if self.n_anchor is None or fields[self.n_anchor.name] is None:
393 return composite_object.location
394 else:
395 return fields[self.n_anchor.name].location
397 def perform(self, mh, composite_object, gstab):
398 # lobster-trace: LRM.Check_Messages
399 # lobster-trace: LRM.Check_Severity
400 assert isinstance(mh, Message_Handler)
401 assert isinstance(composite_object, (Record_Object, Tuple_Aggregate))
402 assert isinstance(gstab, Symbol_Table)
404 if isinstance(composite_object, Record_Object):
405 result = self.n_expr.evaluate(mh, copy(composite_object.field), gstab)
406 else:
407 result = self.n_expr.evaluate(mh, copy(composite_object.value), gstab)
408 if result.value is None:
409 loc = self.get_real_location(composite_object)
410 mh.error(
411 loc,
412 "check %s (%s) evaluates to null"
413 % (self.n_expr.to_string(), mh.cross_file_reference(self.location)),
414 )
416 assert isinstance(result.value, bool)
418 if not result.value:
419 loc = self.get_real_location(composite_object)
420 if self.severity == "warning":
421 mh.warning(
422 location=loc,
423 message=self.message,
424 explanation=self.extrainfo,
425 user=True,
426 )
427 else:
428 mh.error(
429 location=loc,
430 message=self.message,
431 explanation=self.extrainfo,
432 fatal=self.severity == "fatal",
433 user=True,
434 )
435 return False
437 return True
440##############################################################################
441# AST Nodes (Expressions)
442##############################################################################
445class Unary_Operator(Enum):
446 # lobster-exclude: Utility enumeration for unary operators
447 MINUS = auto()
448 PLUS = auto()
449 LOGICAL_NOT = auto()
450 ABSOLUTE_VALUE = auto()
452 STRING_LENGTH = auto()
453 ARRAY_LENGTH = auto()
455 CONVERSION_TO_INT = auto()
456 CONVERSION_TO_DECIMAL = auto()
459class Binary_Operator(Enum):
460 # lobster-exclude: Utility enumeration for binary operators
461 LOGICAL_AND = auto() # Short-circuit
462 LOGICAL_OR = auto() # Short-circuit
463 LOGICAL_XOR = auto()
464 LOGICAL_IMPLIES = auto() # Short-circuit
466 COMP_EQ = auto()
467 COMP_NEQ = auto()
468 COMP_LT = auto()
469 COMP_LEQ = auto()
470 COMP_GT = auto()
471 COMP_GEQ = auto()
473 STRING_CONTAINS = auto()
474 STRING_STARTSWITH = auto()
475 STRING_ENDSWITH = auto()
476 STRING_REGEX = auto()
478 ARRAY_CONTAINS = auto()
480 PLUS = auto()
481 MINUS = auto()
482 TIMES = auto()
483 DIVIDE = auto()
484 REMAINDER = auto()
486 POWER = auto()
488 INDEX = auto()
491class Expression(Node, metaclass=ABCMeta):
492 """Abstract base class for all expressions.
494 :attribute typ: The type of this expression (or None for null values)
495 :type: Type
496 """
498 def __init__(self, location, typ):
499 # lobster-exclude: Constructor only declares variables
500 super().__init__(location)
501 assert typ is None or isinstance(typ, Type)
502 self.typ = typ
504 def evaluate(self, mh, context, gstab): # pragma: no cover
505 """Evaluate the expression in the given context
507 The context can be None, in which case the expression is
508 evaluated in a static context. Otherwise it must be a
509 dictionary that maps names (such as record fields or
510 quantified variables) to expressions.
512 The global symbol table must be None (for static context
513 evaluations), otherwise it must contain the global symbol
514 table to resolve record references.
516 :param mh: the message handler to use
517 :type mh: Message_Handler
518 :param context: name mapping or None (for a static context)
519 :type context: dict[str, Expression]
520 :raise TRLC_Error: if the expression cannot be evaluated
521 :return: result of the evaluation
522 :rtype: Value
524 """
525 assert isinstance(mh, Message_Handler)
526 assert context is None or isinstance(context, dict)
527 assert gstab is None or isinstance(gstab, Symbol_Table)
528 assert False, "evaluate not implemented for %s" % self.__class__.__name__
530 @abstractmethod
531 def to_string(self): # pragma: no cover
532 assert False, "to_string not implemented for %s" % self.__class__.__name__
534 def ensure_type(self, mh, typ):
535 # lobster-trace: LRM.Restricted_Null
536 # lobster-trace: LRM.Null_Is_Invalid
538 assert isinstance(typ, (type, Type))
539 if self.typ is None:
540 mh.error(self.location, "null is not permitted here")
541 elif isinstance(typ, type) and not isinstance(self.typ, typ):
542 mh.error(
543 self.location,
544 "expected expression of type %s, got %s instead"
545 % (typ.__name__, self.typ.__class__.__name__),
546 )
547 elif isinstance(typ, Type) and self.typ != typ:
548 mh.error(
549 self.location,
550 "expected expression of type %s, got %s instead"
551 % (typ.name, self.typ.name),
552 )
554 def resolve_references(self, mh):
555 assert isinstance(mh, Message_Handler)
557 @abstractmethod
558 def can_be_null(self):
559 """Test if the expression could return null
561 Checks the expression if it could generate a null value
562 *without* raising an error. For example `x` could generate a
563 null value if `x` is a record component that is
564 optional. However `x + 1` could not, since an error would
565 occur earlier.
567 :return: possibility of encountering null
568 :rtype: bool
570 """
571 assert False, "can_be_null not implemented for %s" % self.__class__.__name__
573 def uses_field_access(self):
574 """Test if this expression contains a field access on a record or
575 union reference.
577 Returns True if any sub-expression is a
578 :class:`Field_Access_Expression` whose prefix has a
579 :class:`Record_Type` or :class:`Union_Type` type. This is
580 used by the VCG to split checks into "at declaration"
581 (Phase A) and "after references" (Phase B).
583 :return: whether this expression follows a record/union reference
584 :rtype: bool
586 """
587 return False
590class Implicit_Null(Expression):
591 """Synthesised null values
593 When a record object or tuple aggregate is declared and an
594 optional component or field is not specified, we synthesise an
595 implicit null expression for this.
597 For example given this TRLC type::
599 type T {
600 x optional Integer
601 }
603 And this declaration::
605 T Potato {}
607 Then the field mapping for Potato will be::
609 {x: Implicit_Null}
611 Each field will get its own implicit null. Further note that this
612 implicit null is distinct from the explicit :class:`Null_Literal`
613 that can appear in check expressions.
615 """
617 def __init__(self, composite_object, composite_component):
618 # lobster-trace: LRM.Unspecified_Optional_Components
619 assert isinstance(composite_object, (Record_Object, Tuple_Aggregate))
620 assert isinstance(composite_component, Composite_Component)
621 super().__init__(composite_object.location, None)
623 def to_string(self):
624 return "null"
626 def evaluate(self, mh, context, gstab):
627 # lobster-trace: LRM.Unspecified_Optional_Components
628 assert isinstance(mh, Message_Handler)
629 assert context is None or isinstance(context, dict)
630 assert gstab is None or isinstance(gstab, Symbol_Table)
631 return Value(self.location, None, None)
633 def to_python_object(self):
634 return None
636 def dump(self, indent=0): # pragma: no cover
637 # lobster-exclude: Debugging feature
638 self.write_indent(indent, "Implicit_Null")
640 def can_be_null(self):
641 return True
644class Literal(Expression, metaclass=ABCMeta):
645 """Abstract base for all Literals
647 Does not offer any additional features, but it's a nice way to
648 group together all literal types. This is useful if you want to
649 check if you are dealing with a literal::
651 isinstance(my_expression, Literal)
653 """
655 @abstractmethod
656 def to_python_object(self):
657 assert False
660class Null_Literal(Literal):
661 # lobster-trace: LRM.Primary
662 """The null literal
664 This can appear in check expressions::
666 a /= null implies a > 5
667 ^^^^
669 Please note that this is distinct from the :class:`Implicit_Null`
670 values that appear in record objects.
672 """
674 def __init__(self, token):
675 assert isinstance(token, Token)
676 assert token.kind == "KEYWORD"
677 assert token.value == "null"
678 super().__init__(token.location, None)
680 def dump(self, indent=0): # pragma: no cover
681 self.write_indent(indent, "Null Literal")
683 def to_string(self):
684 return "null"
686 def evaluate(self, mh, context, gstab):
687 assert isinstance(mh, Message_Handler)
688 assert context is None or isinstance(context, dict)
689 assert gstab is None or isinstance(gstab, Symbol_Table)
690 return Value(self.location, None, None)
692 def to_python_object(self):
693 return None
695 def can_be_null(self):
696 return True
699class Integer_Literal(Literal):
700 # lobster-trace: LRM.Integer_Values
701 # lobster-trace: LRM.Primary
702 """Integer literals
704 Note that these are always positive. A negative integer is
705 actually a unary negation expression, operating on a positive
706 integer literal::
708 x == -5
710 This would create the following tree::
712 OP_EQUALITY
713 NAME_REFERENCE x
714 UNARY_EXPRESSION -
715 INTEGER_LITERAL 5
717 :attribute value: the non-negative integer value
718 :type: int
719 """
721 def __init__(self, token, typ):
722 assert isinstance(token, Token)
723 assert token.kind == "INTEGER"
724 assert isinstance(typ, Builtin_Integer)
725 super().__init__(token.location, typ)
727 self.value = token.value
729 def dump(self, indent=0): # pragma: no cover
730 self.write_indent(indent, f"Integer Literal {self.value}")
732 def to_string(self):
733 return str(self.value)
735 def evaluate(self, mh, context, gstab):
736 assert isinstance(mh, Message_Handler)
737 assert context is None or isinstance(context, dict)
738 assert gstab is None or isinstance(gstab, Symbol_Table)
739 return Value(self.location, self.value, self.typ)
741 def to_python_object(self):
742 return self.value
744 def can_be_null(self):
745 return False
748class Decimal_Literal(Literal):
749 # lobster-trace: LRM.Decimal_Values
750 # lobster-trace: LRM.Primary
751 """Decimal literals
753 Note that these are always positive. A negative decimal is
754 actually a unary negation expression, operating on a positive
755 decimal literal::
757 x == -5.0
759 This would create the following tree::
761 OP_EQUALITY
762 NAME_REFERENCE x
763 UNARY_EXPRESSION -
764 DECIMAL_LITERAL 5.0
766 :attribute value: the non-negative decimal value
767 :type: fractions.Fraction
768 """
770 def __init__(self, token, typ):
771 assert isinstance(token, Token)
772 assert token.kind == "DECIMAL"
773 assert isinstance(typ, Builtin_Decimal)
774 super().__init__(token.location, typ)
776 self.value = token.value
778 def dump(self, indent=0): # pragma: no cover
779 self.write_indent(indent, f"Decimal Literal {self.value}")
781 def to_string(self):
782 return str(self.value)
784 def evaluate(self, mh, context, gstab):
785 assert isinstance(mh, Message_Handler)
786 assert context is None or isinstance(context, dict)
787 assert gstab is None or isinstance(gstab, Symbol_Table)
788 return Value(self.location, self.value, self.typ)
790 def to_python_object(self):
791 return float(self.value)
793 def can_be_null(self):
794 return False
797class String_Literal(Literal):
798 # lobster-trace: LRM.String_Values
799 # lobster-trace: LRM.Markup_String_Values
800 # lobster-trace: LRM.Primary
801 """String literals
803 Note the value of the string does not include the quotation marks,
804 and any escape sequences are fully resolved. For example::
806 "foo\\"bar"
808 Will have a value of ``foo"bar``.
810 :attribute value: string content
811 :type: str
813 :attribute references: resolved references of a markup string
814 :type: list[Record_Reference]
816 """
818 def __init__(self, token, typ):
819 assert isinstance(token, Token)
820 assert token.kind == "STRING"
821 assert isinstance(typ, Builtin_String)
822 super().__init__(token.location, typ)
824 self.value = token.value
825 self.has_references = isinstance(typ, Builtin_Markup_String)
826 self.references = []
828 def dump(self, indent=0): # pragma: no cover
829 self.write_indent(indent, f"String Literal {repr(self.value)}")
830 if self.has_references:
831 self.write_indent(indent + 1, "Markup References")
832 for ref in self.references:
833 ref.dump(indent + 2)
835 def to_string(self):
836 return self.value
838 def evaluate(self, mh, context, gstab):
839 assert isinstance(mh, Message_Handler)
840 assert context is None or isinstance(context, dict)
841 assert gstab is None or isinstance(gstab, Symbol_Table)
842 return Value(self.location, self.value, self.typ)
844 def to_python_object(self):
845 return self.value
847 def resolve_references(self, mh):
848 assert isinstance(mh, Message_Handler)
849 for ref in self.references:
850 ref.resolve_references(mh)
852 def can_be_null(self):
853 return False
856class Boolean_Literal(Literal):
857 # lobster-trace: LRM.Boolean_Values
858 # lobster-trace: LRM.Primary
859 """Boolean values
861 :attribute value: the boolean value
862 :type: bool
863 """
865 def __init__(self, token, typ):
866 assert isinstance(token, Token)
867 assert token.kind == "KEYWORD"
868 assert token.value in ("false", "true")
869 assert isinstance(typ, Builtin_Boolean)
870 super().__init__(token.location, typ)
872 self.value = token.value == "true"
874 def dump(self, indent=0): # pragma: no cover
875 self.write_indent(indent, f"Boolean Literal {self.value}")
877 def to_string(self):
878 return str(self.value)
880 def evaluate(self, mh, context, gstab):
881 assert isinstance(mh, Message_Handler)
882 assert context is None or isinstance(context, dict)
883 assert gstab is None or isinstance(gstab, Symbol_Table)
884 return Value(self.location, self.value, self.typ)
886 def to_python_object(self):
887 return self.value
889 def can_be_null(self):
890 return False
893class Enumeration_Literal(Literal):
894 """Enumeration values
896 Note that this is distinct from
897 :class:`Enumeration_Literal_Spec`. An enumeration literal is a
898 specific mention of an enumeration member in an expression::
900 foo != my_enum.POTATO
901 ^^^^^^^^^^^^^^
903 To get to the string value of the enumeration literal
904 (i.e. ``POTATO`` here) you can get the name of the literal spec
905 itself: ``enum_lit.value.name``; and to get the name of the
906 enumeration (i.e. ``my_enum`` here) you can use
907 ``enum_lit.value.n_typ.name``.
909 :attribute value: enumeration value
910 :type: Enumeration_Literal_Spec
912 """
914 def __init__(self, location, literal):
915 # lobster-exclude: Constructor only declares variables
916 assert isinstance(literal, Enumeration_Literal_Spec)
917 super().__init__(location, literal.n_typ)
919 self.value = literal
921 def dump(self, indent=0): # pragma: no cover
922 # lobster-exclude: Debugging feature
923 self.write_indent(
924 indent, f"Enumeration Literal {self.typ.name}.{self.value.name}"
925 )
927 def to_string(self):
928 return self.typ.name + "." + self.value.name
930 def evaluate(self, mh, context, gstab):
931 assert isinstance(mh, Message_Handler)
932 assert context is None or isinstance(context, dict)
933 assert gstab is None or isinstance(gstab, Symbol_Table)
934 return Value(self.location, self.value, self.typ)
936 def to_python_object(self):
937 return self.value.name
939 def can_be_null(self):
940 return False
943class Array_Aggregate(Expression):
944 """Instances of array types
946 This is created when assigning to array components::
948 potatoes = ["picasso", "yukon gold", "sweet"]
949 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
951 The type of expression that can be found in an array is somewhat
952 limited:
954 * :class:`Literal`
955 * :class:`Array_Aggregate`
956 * :class:`Record_Reference`
958 :attribute value: contents of the array
959 :type: list[Expression]
961 """
963 def __init__(self, location, typ):
964 # lobster-trace: LRM.Record_Object_Declaration
966 super().__init__(location, typ)
967 self.value = []
969 def dump(self, indent=0): # pragma: no cover
970 # lobster-exclude: Debugging feature
971 self.write_indent(indent, "Array_Aggregate")
972 for n_value in self.value:
973 n_value.dump(indent + 1)
975 def append(self, value):
976 assert isinstance(
977 value,
978 (
979 Literal,
980 Unary_Expression,
981 Array_Aggregate,
982 Tuple_Aggregate,
983 Record_Reference,
984 ),
985 )
986 self.value.append(value)
988 def to_string(self):
989 return "[" + ", ".join(x.to_string() for x in self.value) + "]"
991 def evaluate(self, mh, context, gstab):
992 assert isinstance(mh, Message_Handler)
993 assert context is None or isinstance(context, dict)
994 assert gstab is None or isinstance(gstab, Symbol_Table)
995 return Value(
996 self.location,
997 list(element.evaluate(mh, context, gstab) for element in self.value),
998 self.typ,
999 )
1001 def resolve_references(self, mh):
1002 assert isinstance(mh, Message_Handler)
1004 for val in self.value:
1005 val.resolve_references(mh)
1007 def to_python_object(self):
1008 return [x.to_python_object() for x in self.value]
1010 def can_be_null(self):
1011 return False
1013 def uses_field_access(self):
1014 return any(expr.uses_field_access() for expr in self.value)
1017class Tuple_Aggregate(Expression):
1018 """Instances of a tuple
1020 This is created when assigning to a tuple components. There are
1021 two forms, the ordinary form::
1023 coordinate = (12.3, 40.0)
1024 ^^^^^^^^^^^^
1026 And the separator form::
1028 item = 12345@42
1029 ^^^^^^^^
1031 In terms of AST there is no difference, as the separator is only
1032 syntactic sugar.
1034 :attribute value: contents of the tuple
1035 :type: dict[str, Expression]
1037 """
1039 def __init__(self, location, typ):
1040 # lobster-trace: LRM.Unspecified_Optional_Components
1041 # lobster-trace: LRM.Record_Object_Declaration
1043 super().__init__(location, typ)
1044 self.value = {
1045 n_field.name: Implicit_Null(self, n_field)
1046 for n_field in self.typ.components.values()
1047 }
1049 def assign(self, field, value):
1050 assert isinstance(field, str)
1051 assert isinstance(
1052 value, (Literal, Unary_Expression, Tuple_Aggregate, Record_Reference)
1053 ), "value is %s" % value.__class__.__name__
1054 assert field in self.typ.components
1056 self.value[field] = value
1058 def dump(self, indent=0): # pragma: no cover
1059 # lobster-exclude: Debugging feature
1060 self.write_indent(indent, "Tuple_Aggregate")
1061 self.write_indent(indent + 1, f"Type: {self.typ.name}")
1062 for n_item in self.typ.iter_sequence():
1063 if isinstance(n_item, Composite_Component):
1064 self.value[n_item.name].dump(indent + 1)
1066 def to_string(self):
1067 first = True
1068 if self.typ.has_separators():
1069 rv = ""
1070 else:
1071 rv = "("
1072 for n_item in self.typ.iter_sequence():
1073 if isinstance(n_item, Separator):
1074 rv += " %s " % n_item.token.value
1075 elif first:
1076 first = False
1077 else:
1078 rv += ", "
1080 if isinstance(n_item, Composite_Component):
1081 rv += self.value[n_item.name].to_string()
1082 if self.typ.has_separators():
1083 rv = ""
1084 else:
1085 rv = ")"
1086 return rv
1088 def evaluate(self, mh, context, gstab):
1089 assert isinstance(mh, Message_Handler)
1090 assert context is None or isinstance(context, dict)
1091 assert gstab is None or isinstance(gstab, Symbol_Table)
1092 return Value(
1093 self.location,
1094 {
1095 name: element.evaluate(mh, context, gstab)
1096 for name, element in self.value.items()
1097 },
1098 self.typ,
1099 )
1101 def resolve_references(self, mh):
1102 assert isinstance(mh, Message_Handler)
1104 for val in self.value.values():
1105 val.resolve_references(mh)
1107 def to_python_object(self):
1108 return {name: value.to_python_object() for name, value in self.value.items()}
1110 def can_be_null(self):
1111 return False
1113 def uses_field_access(self):
1114 return any(expr.uses_field_access() for expr in self.value.values())
1117class Record_Reference(Expression):
1118 """Reference to another record object
1120 This can appear in record object declarations::
1122 Requirement Kitten {
1123 depends_on = Other_Package.Cat
1124 ^1 ^2
1125 }
1127 Note that this is distinct from :class:`Record_Object`. It is just
1128 the name; to get to the object referred to by this you can consult
1129 the target attribute.
1131 The reason we have this indirection is that not all names can be
1132 immediately resolved on parsing in the TRLC language.
1134 Note that while the containing package (see 1) is optional in the
1135 source language, the containing package will always be filled in
1136 in this AST node.
1138 :attribute name: The name of the record (see 2)
1139 :type: str
1141 :attribute target: The concrete record object referred to by (2)
1142 :type: Record_Object
1144 :attribute package: The package (see 1) supposed to contain (2)
1145 :type: Package
1147 """
1149 def __init__(self, location, name, typ, package):
1150 # lobster-exclude: Constructor only declares variables
1151 assert isinstance(location, Location)
1152 assert isinstance(name, str)
1153 assert isinstance(typ, (Record_Type, Union_Type)) or typ is None
1154 assert isinstance(package, Package)
1155 super().__init__(location, typ)
1157 self.name = name
1158 self.target = None
1159 self.package = package
1161 def dump(self, indent=0): # pragma: no cover
1162 # lobster-exclude: Debugging feature
1163 self.write_indent(indent, f"Record Reference {self.name}")
1164 self.write_indent(indent + 1, f"Resolved: {self.target is not None}")
1166 def to_string(self):
1167 return self.name
1169 def evaluate(self, mh, context, gstab):
1170 assert isinstance(mh, Message_Handler)
1171 assert context is None or isinstance(context, dict)
1172 assert gstab is None or isinstance(gstab, Symbol_Table)
1173 return Value(self.location, copy(self.target.field), self.typ)
1175 def resolve_references(self, mh):
1176 # lobster-trace: LRM.References_To_Extensions
1177 # lobster-trace: LRM.Union_Type_Minimum_Members
1178 assert isinstance(mh, Message_Handler)
1180 self.target = self.package.symbols.lookup_direct(
1181 mh=mh,
1182 name=self.name,
1183 error_location=self.location,
1184 required_subclass=Record_Object,
1185 )
1186 if self.typ is None:
1187 self.typ = self.target.n_typ
1188 elif isinstance(self.typ, Union_Type):
1189 if not self.typ.is_compatible(self.target.n_typ):
1190 mh.error(
1191 self.location,
1192 "expected reference of type %s,"
1193 " but %s is of type %s"
1194 % (self.typ.name, self.target.name, self.target.n_typ.name),
1195 )
1196 elif not self.target.n_typ.is_subclass_of(self.typ):
1197 mh.error(
1198 self.location,
1199 "expected reference of type %s, but %s is of type %s"
1200 % (self.typ.name, self.target.name, self.target.n_typ.name),
1201 )
1203 def to_python_object(self):
1204 return self.target.fully_qualified_name()
1206 def can_be_null(self):
1207 return False
1210class Name_Reference(Expression):
1211 # lobster-trace: LRM.Qualified_Name
1212 # lobster-trace: LRM.Static_Regular_Expression
1214 """Reference to a name
1216 Name reference to either a :class:`Composite_Component` or a
1217 :class:`Quantified_Variable`. The actual value of course depends
1218 on the context. See :py:meth:`Expression.evaluate()`.
1220 For example::
1222 (forall x in potato => x > 1)
1223 ^1 ^2
1225 Both indicated parts are a :class:`Name_Reference`, the first one
1226 refers to a :class:`Composite_Component`, and the second refers to a
1227 :class:`Quantified_Variable`.
1229 :attribute entity: the entity named here
1230 :type: Composite_Component, Quantified_Variable
1231 """
1233 def __init__(self, location, entity):
1234 assert isinstance(entity, (Composite_Component, Quantified_Variable))
1235 super().__init__(location, entity.n_typ)
1236 self.entity = entity
1238 def dump(self, indent=0): # pragma: no cover
1239 self.write_indent(indent, f"Name Reference to {self.entity.name}")
1241 def to_string(self):
1242 return self.entity.name
1244 def evaluate(self, mh, context, gstab):
1245 assert isinstance(mh, Message_Handler)
1246 assert context is None or isinstance(context, dict)
1247 assert gstab is None or isinstance(gstab, Symbol_Table)
1249 if context is None:
1250 mh.error(self.location, "cannot be used in a static context")
1252 assert self.entity.name in context
1253 return context[self.entity.name].evaluate(mh, context, gstab)
1255 def can_be_null(self):
1256 # The only way we could generate null here (without raising
1257 # error earlier) is when we refer to a component that is
1258 # optional.
1259 if isinstance(self.entity, Composite_Component):
1260 return self.entity.optional
1261 else:
1262 return False
1265class Unary_Expression(Expression):
1266 """Expression with only one operand
1268 This captures the following operations:
1270 * Unary_Operator.PLUS (e.g. ``+5``)
1271 * Unary_Operator.MINUS (e.g. ``-5``)
1272 * Unary_Operator.ABSOLUTE_VALUE (e.g. ``abs 42``)
1273 * Unary_Operator.LOGICAL_NOT (e.g. ``not True``)
1274 * Unary_Operator.STRING_LENGTH (e.g. ``len("foobar")``)
1275 * Unary_Operator.ARRAY_LENGTH (e.g. ``len(component_name)``)
1276 * Unary_Operator.CONVERSION_TO_INT (e.g. ``Integer(5.3)``)
1277 * Unary_Operator.CONVERSION_TO_DECIMAL (e.g. ``Decimal(5)``)
1279 Note that several builtin functions are mapped to unary operators.
1281 :attribute operator: the operation
1282 :type: Unary_Operator
1284 :attribute n_operand: the expression we operate on
1285 :type: Expression
1287 """
1289 def __init__(self, mh, location, typ, operator, n_operand):
1290 # lobster-trace: LRM.Simple_Expression
1291 # lobster-trace: LRM.Relation
1292 # lobster-trace: LRM.Factor
1293 # lobster-trace: LRM.Signature_Len
1294 # lobster-trace: LRM.Signature_Type_Conversion
1296 super().__init__(location, typ)
1297 assert isinstance(mh, Message_Handler)
1298 assert isinstance(operator, Unary_Operator)
1299 assert isinstance(n_operand, Expression)
1300 self.operator = operator
1301 self.n_operand = n_operand
1303 if operator in (
1304 Unary_Operator.MINUS,
1305 Unary_Operator.PLUS,
1306 Unary_Operator.ABSOLUTE_VALUE,
1307 ):
1308 self.n_operand.ensure_type(mh, Builtin_Numeric_Type)
1309 elif operator == Unary_Operator.LOGICAL_NOT:
1310 self.n_operand.ensure_type(mh, Builtin_Boolean)
1311 elif operator == Unary_Operator.STRING_LENGTH:
1312 self.n_operand.ensure_type(mh, Builtin_String)
1313 elif operator == Unary_Operator.ARRAY_LENGTH:
1314 self.n_operand.ensure_type(mh, Array_Type)
1315 elif operator == Unary_Operator.CONVERSION_TO_INT:
1316 self.n_operand.ensure_type(mh, Builtin_Numeric_Type)
1317 elif operator == Unary_Operator.CONVERSION_TO_DECIMAL:
1318 self.n_operand.ensure_type(mh, Builtin_Numeric_Type)
1319 else:
1320 mh.ice_loc(self.location, "unexpected unary operation %s" % operator)
1322 def to_string(self):
1323 prefix_operators = {
1324 Unary_Operator.MINUS: "-",
1325 Unary_Operator.PLUS: "+",
1326 Unary_Operator.ABSOLUTE_VALUE: "abs ",
1327 Unary_Operator.LOGICAL_NOT: "not ",
1328 }
1329 function_calls = {
1330 Unary_Operator.STRING_LENGTH: "len",
1331 Unary_Operator.ARRAY_LENGTH: "len",
1332 Unary_Operator.CONVERSION_TO_INT: "Integer",
1333 Unary_Operator.CONVERSION_TO_DECIMAL: "Decimal",
1334 }
1336 if self.operator in prefix_operators:
1337 return prefix_operators[self.operator] + self.n_operand.to_string()
1339 elif self.operator in function_calls:
1340 return "%s(%s)" % (
1341 function_calls[self.operator],
1342 self.n_operand.to_string(),
1343 )
1345 else:
1346 assert False
1348 def dump(self, indent=0): # pragma: no cover
1349 # lobster-exclude: Debugging feature
1350 self.write_indent(indent, f"Unary {self.operator} Expression")
1351 self.write_indent(indent + 1, f"Type: {self.typ.name}")
1352 self.n_operand.dump(indent + 1)
1354 def evaluate(self, mh, context, gstab):
1355 # lobster-trace: LRM.Null_Is_Invalid
1356 # lobster-trace: LRM.Signature_Len
1357 # lobster-trace: LRM.Signature_Type_Conversion
1358 # lobster-trace: LRM.Len_Semantics
1359 # lobster-trace: LRM.Integer_Conversion_Semantics
1360 # lobster-trace: LRM.Decimal_Conversion_Semantics
1362 assert isinstance(mh, Message_Handler)
1363 assert context is None or isinstance(context, dict)
1364 assert gstab is None or isinstance(gstab, Symbol_Table)
1366 v_operand = self.n_operand.evaluate(mh, context, gstab)
1367 if v_operand.value is None: 1367 ↛ 1368line 1367 didn't jump to line 1368 because the condition on line 1367 was never true
1368 mh.error(
1369 v_operand.location,
1370 "input to unary expression %s (%s) must not be null"
1371 % (self.to_string(), mh.cross_file_reference(self.location)),
1372 )
1374 if self.operator == Unary_Operator.MINUS:
1375 return Value(location=self.location, value=-v_operand.value, typ=self.typ)
1376 elif self.operator == Unary_Operator.PLUS:
1377 return Value(location=self.location, value=+v_operand.value, typ=self.typ)
1378 elif self.operator == Unary_Operator.LOGICAL_NOT:
1379 return Value(
1380 location=self.location, value=not v_operand.value, typ=self.typ
1381 )
1382 elif self.operator == Unary_Operator.ABSOLUTE_VALUE:
1383 return Value(
1384 location=self.location, value=abs(v_operand.value), typ=self.typ
1385 )
1386 elif self.operator in (
1387 Unary_Operator.STRING_LENGTH,
1388 Unary_Operator.ARRAY_LENGTH,
1389 ):
1390 return Value(
1391 location=self.location, value=len(v_operand.value), typ=self.typ
1392 )
1393 elif self.operator == Unary_Operator.CONVERSION_TO_INT:
1394 if isinstance(v_operand.value, Fraction): 1394 ↛ 1401line 1394 didn't jump to line 1401 because the condition on line 1394 was always true
1395 return Value(
1396 location=self.location,
1397 value=math.round_nearest_away(v_operand.value),
1398 typ=self.typ,
1399 )
1400 else:
1401 return Value(
1402 location=self.location, value=v_operand.value, typ=self.typ
1403 )
1404 elif self.operator == Unary_Operator.CONVERSION_TO_DECIMAL:
1405 return Value(
1406 location=self.location, value=Fraction(v_operand.value), typ=self.typ
1407 )
1408 else:
1409 mh.ice_loc(self.location, "unexpected unary operation %s" % self.operator)
1411 def to_python_object(self):
1412 assert self.operator in (Unary_Operator.MINUS, Unary_Operator.PLUS)
1413 val = self.n_operand.to_python_object()
1414 if self.operator == Unary_Operator.MINUS:
1415 return -val
1416 else:
1417 return val
1419 def can_be_null(self):
1420 return False
1422 def uses_field_access(self):
1423 return self.n_operand.uses_field_access()
1426class Binary_Expression(Expression):
1427 """Expression with two operands
1429 This captures the following operations:
1431 * Binary_Operator.LOGICAL_AND (e.g. ``a and b``)
1432 * Binary_Operator.LOGICAL_OR (e.g. ``a or b``)
1433 * Binary_Operator.LOGICAL_XOR (e.g. ``a xor b``)
1434 * Binary_Operator.LOGICAL_IMPLIES (e.g. ``a implies b``)
1435 * Binary_Operator.COMP_EQ (e.g. ``a == null``)
1436 * Binary_Operator.COMP_NEQ (e.g. ``a != null``)
1437 * Binary_Operator.COMP_LT (e.g. ``1 < 2``)
1438 * Binary_Operator.COMP_LEQ (e.g. ``1 <= 2``)
1439 * Binary_Operator.COMP_GT (e.g. ``a > b``)
1440 * Binary_Operator.COMP_GEQ (e.g. ``a >= b``)
1441 * Binary_Operator.STRING_CONTAINS (e.g. ``"foo" in "foobar"``)
1442 * Binary_Operator.STRING_STARTSWITH (e.g. ``startswith("foo", "f")``)
1443 * Binary_Operator.STRING_ENDSWITH (e.g. ``endswith("foo", "o")``)
1444 * Binary_Operator.STRING_REGEX (e.g. ``matches("foo", ".o.``)
1445 * Binary_Operator.ARRAY_CONTAINS (e.g. ``42 in arr``)
1446 * Binary_Operator.PLUS (e.g. ``42 + b`` or ``"foo" + bar``)
1447 * Binary_Operator.MINUS (e.g. ``a - 1``)
1448 * Binary_Operator.TIMES (e.g. ``2 * x``)
1449 * Binary_Operator.DIVIDE (e.g. ``x / 2``)
1450 * Binary_Operator.REMAINDER (e.g. ``x % 2``)
1451 * Binary_Operator.POWER (e.g. ``x ** 2``)
1452 * Binary_Operator.INDEX (e.g. ``foo[2]``)
1454 Note that several builtin functions are mapped to unary operators.
1456 Note also that the plus operation is supported for integers,
1457 rationals and strings.
1459 :attribute operator: the operation
1460 :type: Binary_Operator
1462 :attribute n_lhs: the first operand
1463 :type: Expression
1465 :attribute n_rhs: the second operand
1466 :type: Expression
1468 """
1470 def __init__(self, mh, location, typ, operator, n_lhs, n_rhs):
1471 # lobster-trace: LRM.Expression
1472 # lobster-trace: LRM.Relation
1473 # lobster-trace: LRM.Simple_Expression
1474 # lobster-trace: LRM.Term
1475 # lobster-trace: LRM.Factor
1476 # lobster-trace: LRM.Signature_String_End_Functions
1477 # lobster-trace: LRM.Signature_Matches
1479 super().__init__(location, typ)
1480 assert isinstance(mh, Message_Handler)
1481 assert isinstance(operator, Binary_Operator)
1482 assert isinstance(n_lhs, Expression)
1483 assert isinstance(n_rhs, Expression)
1484 self.operator = operator
1485 self.n_lhs = n_lhs
1486 self.n_rhs = n_rhs
1488 if operator in (
1489 Binary_Operator.LOGICAL_AND,
1490 Binary_Operator.LOGICAL_OR,
1491 Binary_Operator.LOGICAL_XOR,
1492 Binary_Operator.LOGICAL_IMPLIES,
1493 ):
1494 self.n_lhs.ensure_type(mh, Builtin_Boolean)
1495 self.n_rhs.ensure_type(mh, Builtin_Boolean)
1497 elif operator in (Binary_Operator.COMP_EQ, Binary_Operator.COMP_NEQ):
1498 # lobster-trace: LRM.Union_Type_Equality
1499 # lobster-trace: LRM.Union_Type_Equality_Domain
1500 if (self.n_lhs.typ is None) or (self.n_rhs.typ is None):
1501 # We can compary anything to null (including itself)
1502 pass
1503 elif isinstance(self.n_lhs.typ, Union_Type) or isinstance(
1504 self.n_rhs.typ, Union_Type
1505 ):
1506 # For union types, we allow comparison if both
1507 # sides are record-like (Record_Type or Union_Type)
1508 lhs_is_record = isinstance(self.n_lhs.typ, (Record_Type, Union_Type))
1509 rhs_is_record = isinstance(self.n_rhs.typ, (Record_Type, Union_Type))
1510 if not (lhs_is_record and rhs_is_record): 1510 ↛ 1511line 1510 didn't jump to line 1511 because the condition on line 1510 was never true
1511 mh.error(
1512 self.location,
1513 "type mismatch: %s and %s do not match"
1514 % (self.n_lhs.typ.name, self.n_rhs.typ.name),
1515 )
1516 else:
1517 # Check that there is at least one pair of member
1518 # types (one from each side) where one is a subtype
1519 # of the other. This implements Equality_Domain for
1520 # union types: an unrelated record type is rejected.
1521 lhs_members = (
1522 self.n_lhs.typ.types
1523 if isinstance(self.n_lhs.typ, Union_Type)
1524 else [self.n_lhs.typ]
1525 )
1526 rhs_members = (
1527 self.n_rhs.typ.types
1528 if isinstance(self.n_rhs.typ, Union_Type)
1529 else [self.n_rhs.typ]
1530 )
1531 if not any(
1532 lm.is_subclass_of(rm) or rm.is_subclass_of(lm)
1533 for lm in lhs_members
1534 for rm in rhs_members
1535 ):
1536 mh.error(
1537 self.location,
1538 "type mismatch: %s and %s do not match"
1539 % (self.n_lhs.typ.name, self.n_rhs.typ.name),
1540 )
1541 elif self.n_lhs.typ != self.n_rhs.typ:
1542 # Otherwise we can compare anything, as long as the
1543 # types match
1544 mh.error(
1545 self.location,
1546 "type mismatch: %s and %s do not match"
1547 % (self.n_lhs.typ.name, self.n_rhs.typ.name),
1548 )
1550 elif operator in (
1551 Binary_Operator.COMP_LT,
1552 Binary_Operator.COMP_LEQ,
1553 Binary_Operator.COMP_GT,
1554 Binary_Operator.COMP_GEQ,
1555 ):
1556 self.n_lhs.ensure_type(mh, Builtin_Numeric_Type)
1557 self.n_rhs.ensure_type(mh, self.n_lhs.typ)
1559 elif operator in (
1560 Binary_Operator.STRING_CONTAINS,
1561 Binary_Operator.STRING_STARTSWITH,
1562 Binary_Operator.STRING_ENDSWITH,
1563 Binary_Operator.STRING_REGEX,
1564 ):
1565 self.n_lhs.ensure_type(mh, Builtin_String)
1566 self.n_rhs.ensure_type(mh, Builtin_String)
1568 elif operator == Binary_Operator.ARRAY_CONTAINS:
1569 self.n_rhs.ensure_type(mh, Array_Type)
1570 self.n_lhs.ensure_type(mh, self.n_rhs.typ.element_type.__class__)
1572 elif operator == Binary_Operator.PLUS:
1573 if isinstance(self.n_lhs.typ, Builtin_Numeric_Type):
1574 self.n_rhs.ensure_type(mh, self.n_lhs.typ)
1575 else:
1576 self.n_lhs.ensure_type(mh, Builtin_String)
1577 self.n_rhs.ensure_type(mh, Builtin_String)
1579 elif operator in (
1580 Binary_Operator.MINUS,
1581 Binary_Operator.TIMES,
1582 Binary_Operator.DIVIDE,
1583 ):
1584 self.n_lhs.ensure_type(mh, Builtin_Numeric_Type)
1585 self.n_rhs.ensure_type(mh, self.n_lhs.typ)
1587 elif operator == Binary_Operator.POWER:
1588 self.n_lhs.ensure_type(mh, Builtin_Numeric_Type)
1589 self.n_rhs.ensure_type(mh, Builtin_Integer)
1591 elif operator == Binary_Operator.REMAINDER:
1592 self.n_lhs.ensure_type(mh, Builtin_Integer)
1593 self.n_rhs.ensure_type(mh, Builtin_Integer)
1595 elif operator == Binary_Operator.INDEX:
1596 self.n_lhs.ensure_type(mh, Array_Type)
1597 self.n_rhs.ensure_type(mh, Builtin_Integer)
1599 else:
1600 mh.ice_loc(self.location, "unexpected binary operation %s" % operator)
1602 def dump(self, indent=0): # pragma: no cover
1603 # lobster-exclude: Debugging feature
1604 self.write_indent(indent, f"Binary {self.operator} Expression")
1605 self.write_indent(indent + 1, f"Type: {self.typ.name}")
1606 self.n_lhs.dump(indent + 1)
1607 self.n_rhs.dump(indent + 1)
1609 def to_string(self):
1610 infix_operators = {
1611 Binary_Operator.LOGICAL_AND: "and",
1612 Binary_Operator.LOGICAL_OR: "or",
1613 Binary_Operator.LOGICAL_XOR: "xor",
1614 Binary_Operator.LOGICAL_IMPLIES: "implies",
1615 Binary_Operator.COMP_EQ: "==",
1616 Binary_Operator.COMP_NEQ: "!=",
1617 Binary_Operator.COMP_LT: "<",
1618 Binary_Operator.COMP_LEQ: "<=",
1619 Binary_Operator.COMP_GT: ">",
1620 Binary_Operator.COMP_GEQ: ">=",
1621 Binary_Operator.STRING_CONTAINS: "in",
1622 Binary_Operator.ARRAY_CONTAINS: "in",
1623 Binary_Operator.PLUS: "+",
1624 Binary_Operator.MINUS: "-",
1625 Binary_Operator.TIMES: "*",
1626 Binary_Operator.DIVIDE: "/",
1627 Binary_Operator.REMAINDER: "%",
1628 Binary_Operator.POWER: "**",
1629 }
1630 string_functions = {
1631 Binary_Operator.STRING_STARTSWITH: "startswith",
1632 Binary_Operator.STRING_ENDSWITH: "endswith",
1633 Binary_Operator.STRING_REGEX: "matches",
1634 }
1636 if self.operator in infix_operators:
1637 return "%s %s %s" % (
1638 self.n_lhs.to_string(),
1639 infix_operators[self.operator],
1640 self.n_rhs.to_string(),
1641 )
1643 elif self.operator in string_functions:
1644 return "%s(%s, %s)" % (
1645 string_functions[self.operator],
1646 self.n_lhs.to_string(),
1647 self.n_rhs.to_string(),
1648 )
1650 elif self.operator == Binary_Operator.INDEX:
1651 return "%s[%s]" % (self.n_lhs.to_string(), self.n_rhs.to_string())
1653 else:
1654 assert False
1656 def evaluate(self, mh, context, gstab):
1657 # lobster-trace: LRM.Null_Equivalence
1658 # lobster-trace: LRM.Null_Is_Invalid
1659 # lobster-trace: LRM.Signature_String_End_Functions
1660 # lobster-trace: LRM.Signature_Matches
1661 # lobster-trace: LRM.Startswith_Semantics
1662 # lobster-trace: LRM.Endswith_Semantics
1663 # lobster-trace: LRM.Matches_Semantics
1665 assert isinstance(mh, Message_Handler)
1666 assert context is None or isinstance(context, dict)
1667 assert gstab is None or isinstance(gstab, Symbol_Table)
1669 v_lhs = self.n_lhs.evaluate(mh, context, gstab)
1670 if v_lhs.value is None and self.operator not in (
1671 Binary_Operator.COMP_EQ,
1672 Binary_Operator.COMP_NEQ,
1673 ):
1674 mh.error(
1675 v_lhs.location,
1676 "lhs of check %s (%s) must not be null"
1677 % (self.to_string(), mh.cross_file_reference(self.location)),
1678 )
1680 # Check for the short-circuit operators first
1681 if self.operator == Binary_Operator.LOGICAL_AND:
1682 assert isinstance(v_lhs.value, bool)
1683 if v_lhs.value:
1684 return self.n_rhs.evaluate(mh, context, gstab)
1685 else:
1686 return v_lhs
1688 elif self.operator == Binary_Operator.LOGICAL_OR:
1689 assert isinstance(v_lhs.value, bool)
1690 if v_lhs.value:
1691 return v_lhs
1692 else:
1693 return self.n_rhs.evaluate(mh, context, gstab)
1695 elif self.operator == Binary_Operator.LOGICAL_IMPLIES:
1696 assert isinstance(v_lhs.value, bool)
1697 if v_lhs.value:
1698 return self.n_rhs.evaluate(mh, context, gstab)
1699 else:
1700 return Value(location=self.location, value=True, typ=self.typ)
1702 # Otherwise, evaluate RHS and do the operation
1703 v_rhs = self.n_rhs.evaluate(mh, context, gstab)
1704 if v_rhs.value is None and self.operator not in (
1705 Binary_Operator.COMP_EQ,
1706 Binary_Operator.COMP_NEQ,
1707 ):
1708 mh.error(
1709 v_rhs.location,
1710 "rhs of check %s (%s) must not be null"
1711 % (self.to_string(), mh.cross_file_reference(self.location)),
1712 )
1714 if self.operator == Binary_Operator.LOGICAL_XOR:
1715 assert isinstance(v_lhs.value, bool)
1716 assert isinstance(v_rhs.value, bool)
1717 return Value(
1718 location=self.location, value=v_lhs.value ^ v_rhs.value, typ=self.typ
1719 )
1721 elif self.operator == Binary_Operator.COMP_EQ:
1722 return Value(
1723 location=self.location, value=v_lhs.value == v_rhs.value, typ=self.typ
1724 )
1726 elif self.operator == Binary_Operator.COMP_NEQ:
1727 return Value(
1728 location=self.location, value=v_lhs.value != v_rhs.value, typ=self.typ
1729 )
1731 elif self.operator in (
1732 Binary_Operator.COMP_LT,
1733 Binary_Operator.COMP_LEQ,
1734 Binary_Operator.COMP_GT,
1735 Binary_Operator.COMP_GEQ,
1736 ):
1737 return Value(
1738 location=self.location,
1739 value={
1740 Binary_Operator.COMP_LT: lambda lhs, rhs: lhs < rhs,
1741 Binary_Operator.COMP_LEQ: lambda lhs, rhs: lhs <= rhs,
1742 Binary_Operator.COMP_GT: lambda lhs, rhs: lhs > rhs,
1743 Binary_Operator.COMP_GEQ: lambda lhs, rhs: lhs >= rhs,
1744 }[self.operator](v_lhs.value, v_rhs.value),
1745 typ=self.typ,
1746 )
1748 elif self.operator == Binary_Operator.STRING_CONTAINS:
1749 assert isinstance(v_lhs.value, str)
1750 assert isinstance(v_rhs.value, str)
1752 return Value(
1753 location=self.location, value=v_lhs.value in v_rhs.value, typ=self.typ
1754 )
1756 elif self.operator == Binary_Operator.STRING_STARTSWITH:
1757 assert isinstance(v_lhs.value, str)
1758 assert isinstance(v_rhs.value, str)
1759 return Value(
1760 location=self.location,
1761 value=v_lhs.value.startswith(v_rhs.value),
1762 typ=self.typ,
1763 )
1765 elif self.operator == Binary_Operator.STRING_ENDSWITH:
1766 assert isinstance(v_lhs.value, str)
1767 assert isinstance(v_rhs.value, str)
1768 return Value(
1769 location=self.location,
1770 value=v_lhs.value.endswith(v_rhs.value),
1771 typ=self.typ,
1772 )
1774 elif self.operator == Binary_Operator.STRING_REGEX:
1775 assert isinstance(v_lhs.value, str)
1776 assert isinstance(v_rhs.value, str)
1777 return Value(
1778 location=self.location,
1779 value=re.match(v_rhs.value, v_lhs.value) is not None,
1780 typ=self.typ,
1781 )
1783 elif self.operator == Binary_Operator.ARRAY_CONTAINS:
1784 assert isinstance(v_rhs.value, list)
1786 return Value(
1787 location=self.location, value=v_lhs in v_rhs.value, typ=self.typ
1788 )
1790 elif self.operator == Binary_Operator.PLUS:
1791 assert isinstance(v_lhs.value, (int, str, Fraction))
1792 assert isinstance(v_rhs.value, (int, str, Fraction))
1793 return Value(
1794 location=self.location, value=v_lhs.value + v_rhs.value, typ=self.typ
1795 )
1797 elif self.operator == Binary_Operator.MINUS:
1798 assert isinstance(v_lhs.value, (int, Fraction))
1799 assert isinstance(v_rhs.value, (int, Fraction))
1800 return Value(
1801 location=self.location, value=v_lhs.value - v_rhs.value, typ=self.typ
1802 )
1804 elif self.operator == Binary_Operator.TIMES:
1805 assert isinstance(v_lhs.value, (int, Fraction))
1806 assert isinstance(v_rhs.value, (int, Fraction))
1807 return Value(
1808 location=self.location, value=v_lhs.value * v_rhs.value, typ=self.typ
1809 )
1811 elif self.operator == Binary_Operator.DIVIDE:
1812 assert isinstance(v_lhs.value, (int, Fraction))
1813 assert isinstance(v_rhs.value, (int, Fraction))
1815 if v_rhs.value == 0: 1815 ↛ 1816line 1815 didn't jump to line 1816 because the condition on line 1815 was never true
1816 mh.error(
1817 v_rhs.location,
1818 "division by zero in %s (%s)"
1819 % (self.to_string(), mh.cross_file_reference(self.location)),
1820 )
1822 if isinstance(v_lhs.value, int):
1823 return Value(
1824 location=self.location,
1825 value=v_lhs.value // v_rhs.value,
1826 typ=self.typ,
1827 )
1828 else:
1829 return Value(
1830 location=self.location,
1831 value=v_lhs.value / v_rhs.value,
1832 typ=self.typ,
1833 )
1835 elif self.operator == Binary_Operator.REMAINDER:
1836 assert isinstance(v_lhs.value, int)
1837 assert isinstance(v_rhs.value, int)
1839 if v_rhs.value == 0: 1839 ↛ 1840line 1839 didn't jump to line 1840 because the condition on line 1839 was never true
1840 mh.error(
1841 v_rhs.location,
1842 "division by zero in %s (%s)"
1843 % (self.to_string(), mh.cross_file_reference(self.location)),
1844 )
1846 return Value(
1847 location=self.location,
1848 value=math.remainder(v_lhs.value, v_rhs.value),
1849 typ=self.typ,
1850 )
1852 elif self.operator == Binary_Operator.POWER:
1853 assert isinstance(v_lhs.value, (int, Fraction))
1854 assert isinstance(v_rhs.value, int)
1855 return Value(
1856 location=self.location, value=v_lhs.value**v_rhs.value, typ=self.typ
1857 )
1859 elif self.operator == Binary_Operator.INDEX:
1860 assert isinstance(v_lhs.value, list)
1861 assert isinstance(v_rhs.value, int)
1863 if v_rhs.value < 0: 1863 ↛ 1864line 1863 didn't jump to line 1864 because the condition on line 1863 was never true
1864 mh.error(
1865 v_rhs.location,
1866 "index cannot be less than zero in %s (%s)"
1867 % (self.to_string(), mh.cross_file_reference(self.location)),
1868 )
1869 elif ( 1869 ↛ 1873line 1869 didn't jump to line 1873 because the condition on line 1869 was never true
1870 v_lhs.typ.upper_bound is not None
1871 and v_rhs.value > v_lhs.typ.upper_bound
1872 ):
1873 mh.error(
1874 v_rhs.location,
1875 "index cannot be more than %u in %s (%s)"
1876 % (
1877 v_lhs.typ.upper_bound,
1878 self.to_string(),
1879 mh.cross_file_reference(self.location),
1880 ),
1881 )
1882 elif v_rhs.value > len(v_lhs.value): 1882 ↛ 1883line 1882 didn't jump to line 1883 because the condition on line 1882 was never true
1883 mh.error(
1884 v_lhs.location,
1885 "array is not big enough in %s (%s)"
1886 % (self.to_string(), mh.cross_file_reference(self.location)),
1887 )
1889 return Value(
1890 location=self.location,
1891 value=v_lhs.value[v_rhs.value].value,
1892 typ=self.typ,
1893 )
1895 else:
1896 mh.ice_loc(self.location, "unexpected binary operator %s" % self.operator)
1898 def can_be_null(self):
1899 return False
1901 def uses_field_access(self):
1902 return self.n_lhs.uses_field_access() or self.n_rhs.uses_field_access()
1905class Field_Access_Expression(Expression):
1906 """Tuple, Record, or Union field access
1908 For example in::
1910 foo.bar
1911 ^1 ^2
1913 :attribute n_prefix: expression with tuple, record, or union type (see 1)
1914 :type: Expression
1916 :attribute n_field: a field to dereference (see 2)
1917 :type: Composite_Component
1919 :attribute is_union_access: True if the prefix is a union type
1920 :type: bool
1922 :attribute is_universal: True if field exists in all union members.
1923 Only meaningful when is_union_access is True.
1924 :type: bool
1926 """
1928 def __init__(
1929 self, mh, location, n_prefix, n_field, is_union_access=False, is_universal=True
1930 ):
1931 # lobster-trace: LRM.Union_Type_Field_Access
1932 assert isinstance(mh, Message_Handler)
1933 assert isinstance(n_prefix, Expression)
1934 assert isinstance(n_field, Composite_Component)
1935 assert isinstance(is_union_access, bool)
1936 assert isinstance(is_universal, bool)
1937 super().__init__(location, n_field.n_typ)
1938 self.n_prefix = n_prefix
1939 self.n_field = n_field
1940 self.is_union_access = is_union_access
1941 self.is_universal = is_universal
1943 if not is_union_access:
1944 self.n_prefix.ensure_type(mh, self.n_field.member_of)
1946 def dump(self, indent=0): # pragma: no cover
1947 # lobster-exclude: Debugging feature
1948 self.write_indent(indent, f"Field_Access ({self.n_field.name})")
1949 self.n_prefix.dump(indent + 1)
1951 def to_string(self):
1952 return self.n_prefix.to_string() + "." + self.n_field.name
1954 def evaluate(self, mh, context, gstab):
1955 assert isinstance(mh, Message_Handler)
1956 assert context is None or isinstance(context, dict)
1957 assert gstab is None or isinstance(gstab, Symbol_Table)
1959 v_prefix = self.n_prefix.evaluate(mh, context, gstab).value
1960 if v_prefix is None:
1961 # lobster-trace: LRM.Dereference
1962 mh.error(self.n_prefix.location, "null dereference")
1964 # lobster-trace: LRM.Union_Type_Partial_Field_Access
1965 # lobster-trace: LRM.Union_Type_Partial_Field_Null
1966 if self.n_field.name not in v_prefix:
1967 return Value(self.location, None, None)
1969 v_field = v_prefix[self.n_field.name]
1970 if isinstance(v_field, Expression):
1971 # lobster-trace: LRM.Dereference
1972 return v_field.evaluate(mh, context, gstab)
1973 else:
1974 return v_field
1976 def can_be_null(self):
1977 # A union field access on a partial field (not present in all
1978 # member types) evaluates to null at runtime, so we must
1979 # report True in that case.
1980 return self.is_union_access and not self.is_universal
1982 def uses_field_access(self):
1983 # lobster-trace: LRM.Dereference
1984 if isinstance(self.n_prefix.typ, (Record_Type, Union_Type)):
1985 return True
1986 return self.n_prefix.uses_field_access()
1989class Range_Test(Expression):
1990 """Range membership test
1992 For example in::
1994 x in 1 .. field+1
1995 ^lhs ^lower ^^^^^^^upper
1997 Note that none of these are guaranteed to be literals or names;
1998 you can have arbitrarily complex expressions here.
2000 :attribute n_lhs: the expression to test
2001 :type: Expression
2003 :attribute n_lower: the lower bound
2004 :type: Expression
2006 :attribute n_upper: the upper bound
2007 :type: Expression
2009 """
2011 def __init__(self, mh, location, typ, n_lhs, n_lower, n_upper):
2012 # lobster-trace: LRM.Relation
2013 super().__init__(location, typ)
2014 assert isinstance(mh, Message_Handler)
2015 assert isinstance(n_lhs, Expression)
2016 assert isinstance(n_lower, Expression)
2017 assert isinstance(n_upper, Expression)
2018 self.n_lhs = n_lhs
2019 self.n_lower = n_lower
2020 self.n_upper = n_upper
2022 self.n_lhs.ensure_type(mh, Builtin_Numeric_Type)
2023 self.n_lower.ensure_type(mh, self.n_lhs.typ)
2024 self.n_upper.ensure_type(mh, self.n_lhs.typ)
2026 def to_string(self):
2027 return "%s in %s .. %s" % (
2028 self.n_lhs.to_string(),
2029 self.n_lower.to_string(),
2030 self.n_upper.to_string(),
2031 )
2033 def dump(self, indent=0): # pragma: no cover
2034 # lobster-exclude: Debugging feature
2035 self.write_indent(indent, "Range Test")
2036 self.write_indent(indent + 1, f"Type: {self.typ}")
2037 self.n_lhs.dump(indent + 1)
2038 self.n_lower.dump(indent + 1)
2039 self.n_upper.dump(indent + 1)
2041 def evaluate(self, mh, context, gstab):
2042 # lobster-trace: LRM.Null_Is_Invalid
2043 assert isinstance(mh, Message_Handler)
2044 assert context is None or isinstance(context, dict)
2045 assert gstab is None or isinstance(gstab, Symbol_Table)
2047 v_lhs = self.n_lhs.evaluate(mh, context, gstab)
2048 if v_lhs.value is None: 2048 ↛ 2049line 2048 didn't jump to line 2049 because the condition on line 2048 was never true
2049 mh.error(
2050 v_lhs.location,
2051 "lhs of range check %s (%s) see must not be null"
2052 % (self.to_string(), mh.cross_file_reference(self.location)),
2053 )
2055 v_lower = self.n_lower.evaluate(mh, context, gstab)
2056 if v_lower.value is None: 2056 ↛ 2057line 2056 didn't jump to line 2057 because the condition on line 2056 was never true
2057 mh.error(
2058 v_lower.location,
2059 "lower bound of range check %s (%s) must not be null"
2060 % (self.to_string(), mh.cross_file_reference(self.location)),
2061 )
2063 v_upper = self.n_upper.evaluate(mh, context, gstab)
2064 if v_upper.value is None: 2064 ↛ 2065line 2064 didn't jump to line 2065 because the condition on line 2064 was never true
2065 mh.error(
2066 v_upper.location,
2067 "upper bound of range check %s (%s) must not be null"
2068 % (self.to_string(), mh.cross_file_reference(self.location)),
2069 )
2071 return Value(
2072 location=self.location,
2073 value=v_lower.value <= v_lhs.value <= v_upper.value,
2074 typ=self.typ,
2075 )
2077 def can_be_null(self):
2078 return False
2080 def uses_field_access(self):
2081 return (
2082 self.n_lhs.uses_field_access()
2083 or self.n_lower.uses_field_access()
2084 or self.n_upper.uses_field_access()
2085 )
2088class OneOf_Expression(Expression):
2089 """OneOf expression
2091 For example in::
2093 oneof(a, b, c)
2094 ^^^^^^^ choices
2096 :attribute choices: a list of boolean expressions to test
2097 :type: list[Expression]
2098 """
2100 def __init__(self, mh, location, typ, choices):
2101 # lobster-trace: LRM.Signature_OneOf
2102 super().__init__(location, typ)
2103 assert isinstance(typ, Builtin_Boolean)
2104 assert isinstance(mh, Message_Handler)
2105 assert isinstance(choices, list)
2106 assert all(isinstance(item, Expression) for item in choices)
2107 self.choices = choices
2109 for n_choice in choices:
2110 n_choice.ensure_type(mh, Builtin_Boolean)
2112 def to_string(self):
2113 return "oneof(%s)" % ", ".join(
2114 n_choice.to_string() for n_choice in self.choices
2115 )
2117 def dump(self, indent=0): # pragma: no cover
2118 # lobster-exclude: Debugging feature
2119 self.write_indent(indent, "OneOf Test")
2120 self.write_indent(indent + 1, f"Type: {self.typ}")
2121 for n_choice in self.choices:
2122 n_choice.dump(indent + 1)
2124 def evaluate(self, mh, context, gstab):
2125 # lobster-trace: LRM.OneOf_Semantics
2126 assert isinstance(mh, Message_Handler)
2127 assert context is None or isinstance(context, dict)
2128 assert gstab is None or isinstance(gstab, Symbol_Table)
2130 v_choices = [
2131 n_choice.evaluate(mh, context, gstab).value for n_choice in self.choices
2132 ]
2134 return Value(
2135 location=self.location, value=v_choices.count(True) == 1, typ=self.typ
2136 )
2138 def can_be_null(self):
2139 return False
2141 def uses_field_access(self):
2142 return any(n_choice.uses_field_access() for n_choice in self.choices)
2145class Action(Node):
2146 """An if or elseif part inside a conditional expression
2148 Each :class:`Conditional_Expression` is made up of a sequence of
2149 Actions. For example here is a single expression with two
2150 Actions::
2152 (if x == 0 then "zero" elsif x == 1 then "one" else "lots")
2153 ^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^
2155 Note that the else part is not an action, it is an attribute of
2156 the :class:`Conditional_Expression` itself.
2158 :attribute kind: Either if or elseif
2159 :type: str
2161 :attribute n_cond: The boolean condition expression
2162 :type: Expression
2164 :attribute n_expr: The value if the condition evaluates to true
2165 :type: Expression
2167 """
2169 def __init__(self, mh, t_kind, n_condition, n_expression):
2170 # lobster-trace: LRM.Conditional_Expression
2171 assert isinstance(mh, Message_Handler)
2172 assert isinstance(t_kind, Token)
2173 assert t_kind.kind == "KEYWORD"
2174 assert t_kind.value in ("if", "elsif")
2175 assert isinstance(n_condition, Expression)
2176 assert isinstance(n_expression, Expression)
2177 super().__init__(t_kind.location)
2178 self.kind = t_kind.value
2179 self.n_cond = n_condition
2180 self.n_expr = n_expression
2181 # lobster-trace: LRM.Conditional_Expression_Types
2182 self.n_cond.ensure_type(mh, Builtin_Boolean)
2184 def dump(self, indent=0): # pragma: no cover
2185 # lobster-exclude: Debugging feature
2186 self.write_indent(indent, f"{self.kind.capitalize()} Action")
2187 self.write_indent(indent + 1, "Condition")
2188 self.n_cond.dump(indent + 2)
2189 self.write_indent(indent + 1, "Value")
2190 self.n_expr.dump(indent + 2)
2192 def to_string(self):
2193 return "%s %s then %s" % (
2194 self.kind,
2195 self.n_cond.to_string(),
2196 self.n_expr.to_string(),
2197 )
2200class Conditional_Expression(Expression):
2201 """A conditional expression
2203 Each :class:`Conditional_Expression` is made up of a sequence of
2204 one or more :class:`Action`. For example here is a single
2205 expression with two Actions::
2207 (if x == 0 then "zero" elsif x == 1 then "one" else "lots")
2208 ^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^
2210 The else expression is part of the conditional expression itself.
2212 A conditional expression will have at least one action (the if
2213 action), and all other actions will be elsif actions. The else
2214 expression is not optional and will always be present. The types
2215 of all actions and the else expression will match.
2217 :attribute actions: a list of Actions
2218 :type: list[Action]
2220 :attribute else_expr: the else expression
2221 :type: Expression
2223 """
2225 def __init__(self, location, if_action):
2226 # lobster-trace: LRM.Conditional_Expression
2227 assert isinstance(if_action, Action)
2228 assert if_action.kind == "if"
2229 super().__init__(location, if_action.n_expr.typ)
2230 self.actions = [if_action]
2231 self.else_expr = None
2233 def add_elsif(self, mh, n_action):
2234 # lobster-trace: LRM.Conditional_Expression
2235 # lobster-trace; LRM.Conditional_Expression_Types
2236 assert isinstance(mh, Message_Handler)
2237 assert isinstance(n_action, Action)
2238 assert n_action.kind == "elsif"
2240 n_action.n_expr.ensure_type(mh, self.typ)
2241 self.actions.append(n_action)
2243 def set_else_part(self, mh, n_expr):
2244 # lobster-trace: LRM.Conditional_Expression
2245 # lobster-trace; LRM.Conditional_Expression_Types
2246 assert isinstance(mh, Message_Handler)
2247 assert isinstance(n_expr, Expression)
2249 n_expr.ensure_type(mh, self.typ)
2250 self.else_expr = n_expr
2252 def dump(self, indent=0): # pragma: no cover
2253 # lobster-exclude: Debugging feature
2254 self.write_indent(indent, "Conditional expression")
2255 for action in self.actions:
2256 action.dump(indent + 1)
2257 self.write_indent(indent + 1, "Else")
2258 self.else_expr.dump(indent + 2)
2260 def to_string(self):
2261 rv = "(" + " ".join(action.to_string() for action in self.actions)
2262 rv += " else %s" % self.else_expr.to_string()
2263 rv += ")"
2264 return rv
2266 def evaluate(self, mh, context, gstab):
2267 # lobster-trace: LRM.Conditional_Expression_Else
2268 # lobster-trace: LRM.Conditional_Expression_Evaluation
2269 # lobster-trace: LRM.Null_Is_Invalid
2270 assert isinstance(mh, Message_Handler)
2271 assert context is None or isinstance(context, dict)
2272 assert gstab is None or isinstance(gstab, Symbol_Table)
2274 for action in self.actions:
2275 v_cond = action.n_cond.evaluate(mh, context, gstab)
2276 if v_cond.value is None: 2276 ↛ 2277line 2276 didn't jump to line 2277 because the condition on line 2276 was never true
2277 mh.error(
2278 v_cond.location,
2279 "condition of %s (%s) must not be null"
2280 % (action.to_string(), mh.cross_file_reference(self.location)),
2281 )
2282 if v_cond.value:
2283 return action.n_expr.evaluate(mh, context, gstab)
2285 return self.else_expr.evaluate(mh, context, gstab)
2287 def can_be_null(self):
2288 if self.else_expr and self.else_expr.can_be_null():
2289 return True
2291 return any(action.n_expr.can_be_null() for action in self.actions)
2293 def uses_field_access(self):
2294 return any(
2295 action.n_cond.uses_field_access() or action.n_expr.uses_field_access()
2296 for action in self.actions
2297 ) or (self.else_expr is not None and self.else_expr.uses_field_access())
2300class Quantified_Expression(Expression):
2301 """A quantified expression
2303 For example::
2305 (forall x in array_component => x > 0)
2306 ^4 ^1 ^2 ^^^^^3
2308 A quantified expression introduces and binds a
2309 :class:`Quantified_Variable` (see 1) from a specified source (see
2310 2). When the body (see 3) is evaluated, the name of 1 is bound to
2311 each component of the source in turn.
2313 :attribute n_var: The quantified variable (see 1)
2314 :type: Quantified_Variable
2316 :attribute n_source: The array to iterate over (see 2)
2317 :type: Name_Reference
2319 :attribute n_expr: The body of the quantifier (see 3)
2320 :type: Expression
2322 :attribute universal: True means forall, false means exists (see 4)
2323 :type: Boolean
2325 """
2327 def __init__(self, mh, location, typ, universal, n_variable, n_source, n_expr):
2328 # lobster-trace: LRM.Quantified_Expression
2329 # lobster-trace: LRM.Quantification_Type
2330 super().__init__(location, typ)
2331 assert isinstance(typ, Builtin_Boolean)
2332 assert isinstance(universal, bool)
2333 assert isinstance(n_variable, Quantified_Variable)
2334 assert isinstance(n_expr, Expression)
2335 assert isinstance(n_source, Name_Reference)
2336 self.universal = universal
2337 self.n_var = n_variable
2338 self.n_expr = n_expr
2339 self.n_source = n_source
2340 self.n_expr.ensure_type(mh, Builtin_Boolean)
2342 def dump(self, indent=0): # pragma: no cover
2343 # lobster-exclude: Debugging feature
2344 if self.universal:
2345 self.write_indent(indent, "Universal quantified expression")
2346 else:
2347 self.write_indent(indent, "Existential quantified expression")
2348 self.n_var.dump(indent + 1)
2349 self.n_expr.dump(indent + 1)
2351 def to_string(self):
2352 return "(%s %s in %s => %s)" % (
2353 "forall" if self.universal else "exists",
2354 self.n_var.name,
2355 self.n_source.to_string(),
2356 self.n_expr.to_string(),
2357 )
2359 def evaluate(self, mh, context, gstab):
2360 # lobster-trace: LRM.Null_Is_Invalid
2361 # lobster-trace: LRM.Universal_Quantification_Semantics
2362 # lobster-trace: LRM.Existential_Quantification_Semantics
2363 assert isinstance(mh, Message_Handler)
2364 assert context is None or isinstance(context, dict)
2365 assert gstab is None or isinstance(gstab, Symbol_Table)
2367 if context is None: 2367 ↛ 2368line 2367 didn't jump to line 2368 because the condition on line 2367 was never true
2368 new_ctx = {}
2369 else:
2370 new_ctx = copy(context)
2372 # This is going to be a bit tricky. We essentially eliminate
2373 # the quantifier and substitute; for the sake of making better
2374 # error messages.
2375 assert isinstance(self.n_source.entity, Composite_Component)
2376 array_values = context[self.n_source.entity.name]
2377 if isinstance(array_values, Implicit_Null):
2378 mh.error(
2379 array_values.location,
2380 "%s in quantified expression %s (%s) "
2381 "must not be null"
2382 % (
2383 self.n_source.to_string(),
2384 self.to_string(),
2385 mh.cross_file_reference(self.location),
2386 ),
2387 )
2388 else:
2389 assert isinstance(array_values, Array_Aggregate)
2391 rv = self.universal
2392 loc = self.location
2393 for binding in array_values.value:
2394 new_ctx[self.n_var.name] = binding
2395 result = self.n_expr.evaluate(mh, new_ctx, gstab)
2396 assert isinstance(result.value, bool)
2397 if self.universal and not result.value:
2398 rv = False
2399 loc = binding.location
2400 break
2401 elif not self.universal and result.value:
2402 rv = True
2403 loc = binding.location
2404 break
2406 return Value(location=loc, value=rv, typ=self.typ)
2408 def can_be_null(self):
2409 return False
2411 def uses_field_access(self):
2412 return self.n_source.uses_field_access() or self.n_expr.uses_field_access()
2415##############################################################################
2416# AST Nodes (Entities)
2417##############################################################################
2420class Entity(Node, metaclass=ABCMeta):
2421 """Base class for all entities.
2423 An entity is a concrete object (with a name) for which we need to
2424 allocate memory. Examples of entities are types and record
2425 objects.
2427 :attribute name: unqualified name of the entity
2428 :type: str
2430 """
2432 def __init__(self, name, location):
2433 # lobster-trace: LRM.Described_Name_Equality
2434 super().__init__(location)
2435 assert isinstance(name, str)
2436 self.name = name
2439class Typed_Entity(Entity, metaclass=ABCMeta):
2440 """Base class for entities with a type.
2442 A typed entity is a concrete object (with a name and TRLC type)
2443 for which we need to allocate memory. Examples of typed entities
2444 are record objects and components.
2446 :attribute n_typ: type of the entity
2447 :type: Type
2449 """
2451 def __init__(self, name, location, n_typ):
2452 # lobster-exclude: Constructor only declares variables
2453 super().__init__(name, location)
2454 assert isinstance(n_typ, Type)
2455 self.n_typ = n_typ
2458class Quantified_Variable(Typed_Entity):
2459 """Variable used in quantified expression.
2461 A quantified expression declares and binds a variable, for which
2462 we need a named entity. For example in::
2464 (forall x in array => x > 1)
2465 ^
2467 We represent this first x as a :class:`Quantified_Variable`, the
2468 second x will be an ordinary :class:`Name_Reference`.
2470 :attribute typ: type of the variable (i.e. element type of the array)
2471 :type: Type
2473 """
2475 def dump(self, indent=0): # pragma: no cover
2476 # lobster-exclude: Debugging feature
2477 self.write_indent(indent, f"Quantified Variable {self.name}")
2478 self.n_typ.dump(indent + 1)
2481class Type(Entity, metaclass=ABCMeta):
2482 """Abstract base class for all types."""
2484 def perform_type_checks(self, mh, value, gstab):
2485 assert isinstance(mh, Message_Handler)
2486 assert isinstance(value, Expression)
2487 assert isinstance(gstab, Symbol_Table)
2488 return True
2490 def get_example_value(self):
2491 # lobster-exclude: utility method
2492 assert False
2495class Concrete_Type(Type, metaclass=ABCMeta):
2496 # lobster-trace: LRM.Type_Declarations
2497 """Abstract base class for all non-anonymous types.
2499 :attribute n_package: package where this type was declared
2500 :type: Package
2501 """
2503 def __init__(self, name, location, n_package):
2504 super().__init__(name, location)
2505 assert isinstance(n_package, Package)
2506 self.n_package = n_package
2508 def fully_qualified_name(self):
2509 """Return the FQN for this type (i.e. PACKAGE.NAME)
2511 :returns: the type's full name
2512 :rtype: str
2513 """
2514 return self.n_package.name + "." + self.name
2516 def __hash__(self):
2517 return hash((self.n_package.name, self.name))
2519 def __repr__(self):
2520 return "%s<%s>" % (self.__class__.__name__, self.fully_qualified_name())
2523class Builtin_Type(Type, metaclass=ABCMeta):
2524 # lobster-trace: LRM.Builtin_Types
2525 """Abstract base class for all builtin types."""
2527 LOCATION = Location(file_name="<builtin>")
2529 def __init__(self, name):
2530 super().__init__(name, Builtin_Type.LOCATION)
2532 def dump(self, indent=0): # pragma: no cover
2533 self.write_indent(indent, self.__class__.__name__)
2536class Builtin_Numeric_Type(Builtin_Type, metaclass=ABCMeta):
2537 # lobster-trace: LRM.Builtin_Types
2538 """Abstract base class for all builtin numeric types."""
2540 def dump(self, indent=0): # pragma: no cover
2541 self.write_indent(indent, self.__class__.__name__)
2544class Builtin_Function(Entity):
2545 # lobster-trace: LRM.Builtin_Functions
2546 """Builtin functions.
2548 These are auto-generated by the :class:`~trlc.trlc.Source_Manager`.
2550 :attribute arity: number of parameters
2551 :type: int
2553 :attribute arity_at_least: when true, arity indicates a lower bound
2554 :type: bool
2556 """
2558 LOCATION = Location(file_name="<builtin>")
2560 def __init__(self, name, arity, arity_at_least=False):
2561 super().__init__(name, Builtin_Function.LOCATION)
2562 assert isinstance(arity, int)
2563 assert isinstance(arity_at_least, bool)
2564 assert arity >= 0
2565 self.arity = arity
2566 self.arity_at_least = arity_at_least
2568 def dump(self, indent=0): # pragma: no cover
2569 self.write_indent(indent, self.__class__.__name__ + " " + self.name)
2572class Array_Type(Type):
2573 """Anonymous array type.
2575 These are declared implicitly for each record component that has
2576 an array specifier::
2578 foo Integer [5 .. *]
2579 ^
2581 :attribute lower_bound: minimum number of elements
2582 :type: int
2584 :attribute loc_lower: text location of the lower bound indicator
2585 :type: Location
2587 :attribute upper_bound: maximum number of elements (or None)
2588 :type: int
2590 :attribute loc_upper: text location of the upper bound indicator
2591 :type: Location
2593 :attribute element_type: type of the array elements
2594 :type: Type
2596 """
2598 def __init__(
2599 self, location, element_type, loc_lower, lower_bound, loc_upper, upper_bound
2600 ):
2601 # lobster-exclude: Constructor only declares variables
2602 assert isinstance(element_type, Type) or element_type is None
2603 assert isinstance(lower_bound, int)
2604 assert lower_bound >= 0
2605 assert upper_bound is None or isinstance(upper_bound, int)
2606 assert upper_bound is None or upper_bound >= 0
2607 assert isinstance(loc_lower, Location)
2608 assert isinstance(loc_upper, Location)
2610 if element_type is None: 2610 ↛ 2611line 2610 didn't jump to line 2611 because the condition on line 2610 was never true
2611 name = "universal array"
2612 elif upper_bound is None:
2613 if lower_bound == 0:
2614 name = "array of %s" % element_type.name
2615 else:
2616 name = "array of at least %u %s" % (lower_bound, element_type.name)
2617 elif lower_bound == upper_bound:
2618 name = "array of %u %s" % (lower_bound, element_type.name)
2619 else:
2620 name = "array of %u to %u %s" % (
2621 lower_bound,
2622 upper_bound,
2623 element_type.name,
2624 )
2625 super().__init__(name, location)
2626 self.lower_bound = lower_bound
2627 self.loc_lower = loc_lower
2628 self.upper_bound = upper_bound
2629 self.loc_upper = loc_upper
2630 self.element_type = element_type
2632 def dump(self, indent=0): # pragma: no cover
2633 # lobster-exclude: Debugging feature
2634 self.write_indent(indent, "Array_Type")
2635 self.write_indent(indent + 1, f"Lower bound: {self.lower_bound}")
2636 if self.upper_bound is None:
2637 self.write_indent(indent + 1, "Upper bound: *")
2638 else:
2639 self.write_indent(indent + 1, f"Upper bound: {self.upper_bound}")
2640 self.write_indent(indent + 1, f"Element type: {self.element_type.name}")
2642 def perform_type_checks(self, mh, value, gstab):
2643 assert isinstance(mh, Message_Handler)
2644 assert isinstance(gstab, Symbol_Table)
2646 if isinstance(value, Array_Aggregate):
2647 return all(
2648 self.element_type.perform_type_checks(mh, v, gstab) for v in value.value
2649 )
2650 else:
2651 assert isinstance(value, Implicit_Null)
2652 return True
2654 def get_example_value(self):
2655 # lobster-exclude: utility method
2656 return "[%s]" % self.element_type.get_example_value()
2659class Union_Type(Type):
2660 # lobster-trace: LRM.union_type
2661 # lobster-trace: LRM.Union_Type_Minimum_Members
2662 # lobster-trace: LRM.Union_Type_Record_Types_Only
2663 """Anonymous union type for record references.
2665 These are declared implicitly when a record component specifies
2666 multiple allowed record types using bracket syntax::
2668 parent [Systemrequirement, Codebeamerrequirement]
2669 ^
2671 :attribute types: the allowed record types
2672 :type: list[Record_Type]
2674 """
2676 def __init__(self, location, types):
2677 assert isinstance(types, list)
2678 assert len(types) >= 1
2679 assert all(isinstance(t, Record_Type) for t in types)
2680 name = "[%s]" % ", ".join(t.name for t in types)
2681 super().__init__(name, location)
2682 self.types = types
2683 self._field_map = None
2685 def get_field_map(self):
2686 # lobster-trace: LRM.Union_Type_Field_Access
2687 """Compute accessible fields across all union members.
2689 Returns a dict mapping field name to a dict with keys:
2691 * ``component``: a representative Composite_Component
2692 * ``n_typ``: the field type (None if conflicting)
2693 * ``count``: how many member types have this field
2694 * ``total``: total number of member types
2695 * ``optional_in_any``: True if optional in at least one member
2697 :rtype: dict[str, dict]
2698 """
2699 if self._field_map is not None:
2700 return self._field_map
2702 field_map = {}
2703 for record_type in self.types:
2704 seen_in_type = set()
2705 for comp in record_type.all_components():
2706 if comp.name in seen_in_type: 2706 ↛ 2707line 2706 didn't jump to line 2707 because the condition on line 2706 was never true
2707 continue
2708 seen_in_type.add(comp.name)
2709 if comp.name not in field_map:
2710 field_map[comp.name] = {
2711 "component": comp,
2712 "n_typ": comp.n_typ,
2713 "count": 1,
2714 "total": len(self.types),
2715 "optional_in_any": comp.optional,
2716 }
2717 else:
2718 info = field_map[comp.name]
2719 info["count"] += 1
2720 # Type identity (is) is correct here:
2721 # non-union type objects are structural
2722 # singletons in the symbol table, so
2723 # identity comparison is both correct and
2724 # cheap.
2725 if info["n_typ"] is not comp.n_typ:
2726 info["n_typ"] = None # type conflict
2727 if comp.optional: 2727 ↛ 2728line 2727 didn't jump to line 2728 because the condition on line 2727 was never true
2728 info["optional_in_any"] = True
2730 self._field_map = field_map
2731 return self._field_map
2733 def dump(self, indent=0): # pragma: no cover
2734 # lobster-exclude: Debugging feature
2735 self.write_indent(indent, "Union_Type")
2736 for t in self.types:
2737 self.write_indent(indent + 1, t.name)
2739 def perform_type_checks(self, mh, value, gstab):
2740 # Union types have no checks of their own; type validation
2741 # happens in Record_Reference.resolve_references() via
2742 # is_compatible(). Returning True unconditionally is
2743 # intentional.
2744 assert isinstance(mh, Message_Handler)
2745 assert isinstance(value, Expression)
2746 assert isinstance(gstab, Symbol_Table)
2747 return True
2749 def is_compatible(self, record_type):
2750 """Test if the given record type is accepted by this union.
2752 :param record_type: type to check
2753 :type record_type: Record_Type
2755 :returns: true if the type is or extends one of the union members
2756 :rtype: bool
2757 """
2758 assert isinstance(record_type, Record_Type)
2759 return any(record_type.is_subclass_of(t) for t in self.types)
2761 def get_example_value(self):
2762 # lobster-exclude: utility method
2763 return "%s_instance" % self.types[0].name
2766class Builtin_Integer(Builtin_Numeric_Type):
2767 # lobster-trace: LRM.Builtin_Types
2768 # lobster-trace: LRM.Integer_Values
2769 """Builtin integer type."""
2771 def __init__(self):
2772 super().__init__("Integer")
2774 def get_example_value(self):
2775 # lobster-exclude: utility method
2776 return "100"
2779class Builtin_Decimal(Builtin_Numeric_Type):
2780 # lobster-trace: LRM.Builtin_Types
2781 # lobster-trace: LRM.Decimal_Values
2782 """Builtin decimal type."""
2784 def __init__(self):
2785 super().__init__("Decimal")
2787 def get_example_value(self):
2788 # lobster-exclude: utility method
2789 return "3.14"
2792class Builtin_Boolean(Builtin_Type):
2793 # lobster-trace: LRM.Builtin_Types
2794 # lobster-trace: LRM.Boolean_Values
2795 """Builtin boolean type."""
2797 def __init__(self):
2798 super().__init__("Boolean")
2800 def get_example_value(self):
2801 # lobster-exclude: utility method
2802 return "true"
2805class Builtin_String(Builtin_Type):
2806 # lobster-trace: LRM.Builtin_Types
2807 # lobster-trace: LRM.String_Values
2808 """Builtin string type."""
2810 def __init__(self):
2811 super().__init__("String")
2813 def get_example_value(self):
2814 # lobster-exclude: utility method
2815 return '"potato"'
2818class Builtin_Markup_String(Builtin_String):
2819 # lobster-trace: LRM.Builtin_Types
2820 # lobster-trace: LRM.Markup_String_Values
2821 """Builtin string type that allows checked references to TRLC
2822 objects.
2823 """
2825 def __init__(self):
2826 super().__init__()
2827 self.name = "Markup_String"
2829 def get_example_value(self):
2830 # lobster-exclude: utility method
2831 return '"also see [[potato]]"'
2834class Package(Entity):
2835 """Packages.
2837 A package is declared when it is first encountered (in either a
2838 rsl or trlc file). A package contains all symbols declared in it,
2839 both types and record objects. A package is not associated with
2840 just a single file, it can be spread over multiple files.
2842 :attribute declared_late: indicates if this package is declared in a \
2843 trlc file
2844 :type: bool
2846 :attribute symbols: symbol table of the package
2847 :type: Symbol_Table
2849 """
2851 def __init__(self, name, location, builtin_stab, declared_late):
2852 # lobster-exclude: Constructor only declares variables
2853 super().__init__(name, location)
2854 assert isinstance(builtin_stab, Symbol_Table)
2855 assert isinstance(declared_late, bool)
2856 self.symbols = Symbol_Table()
2857 self.symbols.make_visible(builtin_stab)
2858 self.declared_late = declared_late
2860 def dump(self, indent=0): # pragma: no cover
2861 # lobster-exclude: Debugging feature
2862 self.write_indent(indent, f"Package {self.name}")
2863 self.write_indent(indent + 1, f"Declared_Late: {self.declared_late}")
2864 self.symbols.dump(indent + 1, omit_heading=True)
2866 def __repr__(self):
2867 return "%s<%s>" % (self.__class__.__name__, self.name)
2870class Composite_Type(Concrete_Type, metaclass=ABCMeta):
2871 """Abstract base for record and tuple types, as they share some
2872 functionality.
2874 :attribute components: type components (including inherited if applicable)
2875 :type: Symbol_Table[Composite_Component]
2877 :attribute description: user-supplied description of the type or None
2878 :type: str
2880 :attribute checks: user-defined checks for this type (excluding \
2881 inherited checks)
2882 :type: list[Check]
2884 """
2886 def __init__(self, name, description, location, package, inherited_symbols=None):
2887 # lobster-trace: LRM.Described_Name_Description
2888 super().__init__(name, location, package)
2889 assert isinstance(description, str) or description is None
2890 assert isinstance(inherited_symbols, Symbol_Table) or inherited_symbols is None
2892 self.components = Symbol_Table(inherited_symbols)
2893 self.description = description
2894 self.checks = []
2896 def add_check(self, n_check):
2897 # lobster-trace: LRM.Check_Evaluation_Order
2898 assert isinstance(n_check, Check)
2899 self.checks.append(n_check)
2901 def iter_checks(self):
2902 # lobster-trace: LRM.Check_Evaluation_Order
2903 yield from self.checks
2905 def all_components(self):
2906 # lobster-exclude: Convenience function
2907 """Convenience function to get a list of all components.
2909 :rtype: list[Composite_Component]
2910 """
2911 return list(self.components.table.values())
2914class Composite_Component(Typed_Entity):
2915 """Component in a record or tuple.
2917 When declaring a composite type, for each component an entity is
2918 declared::
2920 type|tuple T {
2921 foo "blah" optional Boolean
2922 ^1 ^2 ^3 ^4
2924 :attribute description: optional text (see 2) for this component, or None
2925 :type: str
2927 :attribute member_of: a link back to the containing record or tuple; \
2928 for inherited fields this refers back to the original base record type
2929 :type: Composite_Type
2931 :attribute optional: indicates if the component can be null or not (see 3)
2932 :type: bool
2934 """
2936 def __init__(self, name, description, location, member_of, n_typ, optional):
2937 # lobster-trace: LRM.Described_Name_Description
2938 super().__init__(name, location, n_typ)
2939 assert isinstance(description, str) or description is None
2940 assert isinstance(member_of, Composite_Type)
2941 assert isinstance(optional, bool)
2942 self.description = description
2943 self.member_of = member_of
2944 self.optional = optional
2946 def dump(self, indent=0): # pragma: no cover
2947 # lobster-exclude: Debugging feature
2948 self.write_indent(indent, f"Composite_Component {self.name}")
2949 if self.description:
2950 self.write_indent(indent + 1, f"Description: {self.description}")
2951 self.write_indent(indent + 1, f"Optional: {self.optional}")
2952 self.write_indent(indent + 1, f"Type: {self.n_typ.name}")
2954 def __repr__(self):
2955 return "%s<%s>" % (
2956 self.__class__.__name__,
2957 self.member_of.fully_qualified_name() + "." + self.name,
2958 )
2961class Record_Type(Composite_Type):
2962 """A user-defined record type.
2964 In this example::
2966 type T "optional description of T" extends Root_T {
2967 ^1 ^2 ^3
2969 Note that (1) is part of the :class:`Entity` base, and (2) is part
2970 of the :class:`Composite_Type` base.
2972 :attribute parent: root type or None, indicated by (3) above
2973 :type: Record_Type
2975 :attribute frozen: mapping of frozen components
2976 :type: dict[str, Expression]
2978 :attribute is_final: type is final (i.e. no new components may be declared)
2979 :type: bool
2981 :attribute is_abstract: type is abstract
2982 :type: bool
2984 """
2986 def __init__(self, name, description, location, package, n_parent, is_abstract):
2987 # lobster-exclude: Constructor only declares variables
2988 assert isinstance(n_parent, Record_Type) or n_parent is None
2989 assert isinstance(is_abstract, bool)
2990 super().__init__(
2991 name,
2992 description,
2993 location,
2994 package,
2995 n_parent.components if n_parent else None,
2996 )
2997 self.parent = n_parent
2998 self.frozen = {}
2999 self.is_final = n_parent.is_final if n_parent else False
3000 self.is_abstract = is_abstract
3002 def iter_checks(self):
3003 # lobster-trace: LRM.Check_Evaluation_Order
3004 # lobster-trace: LRM.Check_Evaluation_Order_For_Extensions
3005 if self.parent:
3006 yield from self.parent.iter_checks()
3007 yield from self.checks
3009 def dump(self, indent=0): # pragma: no cover
3010 # lobster-exclude: Debugging feature
3011 self.write_indent(indent, f"Record_Type {self.name}")
3012 if self.description:
3013 self.write_indent(indent + 1, f"Description: {self.description}")
3014 if self.parent:
3015 self.write_indent(indent + 1, f"Parent: {self.parent.name}")
3016 self.components.dump(indent + 1, omit_heading=True)
3017 if self.checks:
3018 self.write_indent(indent + 1, "Checks")
3019 for n_check in self.checks:
3020 n_check.dump(indent + 2)
3021 else:
3022 self.write_indent(indent + 1, "Checks: None")
3024 def all_components(self):
3025 """Convenience function to get a list of all components.
3027 :rtype: list[Composite_Component]
3028 """
3029 if self.parent:
3030 return self.parent.all_components() + list(self.components.table.values())
3031 else:
3032 return list(self.components.table.values())
3034 def is_subclass_of(self, record_type):
3035 """Checks if this record type is or inherits from the given type
3037 :param record_type: check if are or extend this type
3038 :type record_type: Record_Type
3040 :returns: true if we are or extend the given type
3041 :rtype: Boolean
3042 """
3043 assert isinstance(record_type, Record_Type)
3045 ptr = self
3046 while ptr:
3047 if ptr is record_type:
3048 return True
3049 else:
3050 ptr = ptr.parent
3051 return False
3053 def is_frozen(self, n_component):
3054 """Test if the given component is frozen.
3056 :param n_component: a composite component of this record type \
3057 (or any of its parents)
3058 :type n_component: Composite_Component
3060 :rtype: bool
3061 """
3062 assert isinstance(n_component, Composite_Component)
3063 if n_component.name in self.frozen:
3064 return True
3065 elif self.parent:
3066 return self.parent.is_frozen(n_component)
3067 else:
3068 return False
3070 def get_freezing_expression(self, n_component):
3071 """Retrieve the frozen value for a frozen component
3073 It is an internal compiler error to call this method with a
3074 component that his not frozen.
3076 :param n_component: a frozen component of this record type \
3077 (or any of its parents)
3078 :type n_component: Composite_Component
3080 :rtype: Expression
3082 """
3083 assert isinstance(n_component, Composite_Component)
3084 if n_component.name in self.frozen: 3084 ↛ 3086line 3084 didn't jump to line 3086 because the condition on line 3084 was always true
3085 return self.frozen[n_component.name]
3086 elif self.parent:
3087 return self.parent.get_freezing_expression(n_component)
3088 else:
3089 assert False
3091 def get_example_value(self):
3092 # lobster-exclude: utility method
3093 return "%s_instance" % self.name
3096class Tuple_Type(Composite_Type):
3097 """A user-defined tuple type.
3099 In this example::
3101 tuple T "optional description of T" {
3102 ^1 ^2
3104 Note that (1) is part of the :class:`Entity` base, and (2) is part
3105 of the :class:`Composite_Type` base.
3107 :attribute separators: list of syntactic separators.
3108 :type: list[Separator]
3110 Note the list of separators will either be empty, or there will be
3111 precisely one less separator than components.
3113 """
3115 def __init__(self, name, description, location, package):
3116 # lobster-trace: LRM.Tuple_Declaration
3117 super().__init__(name, description, location, package)
3118 self.separators = []
3120 def add_separator(self, n_separator):
3121 # lobster-exclude: utility method
3122 assert isinstance(n_separator, Separator)
3123 assert len(self.separators) + 1 == len(self.components.table)
3124 self.separators.append(n_separator)
3126 def iter_separators(self):
3127 """Iterate over all separators"""
3128 # lobster-exclude: utility method
3129 yield from self.separators
3131 def iter_sequence(self):
3132 """Iterate over all components and separators in syntactic order"""
3133 # lobster-exclude: utility method
3134 if self.separators:
3135 for i, n_component in enumerate(self.components.table.values()):
3136 yield n_component
3137 if i < len(self.separators):
3138 yield self.separators[i]
3139 else:
3140 yield from self.components.table.values()
3142 def has_separators(self):
3143 """Returns true if a tuple type requires separators"""
3144 # lobster-exclude: utility method
3145 return bool(self.separators)
3147 def dump(self, indent=0): # pragma: no cover
3148 # lobster-exclude: Debugging feature
3149 self.write_indent(indent, f"Tuple_Type {self.name}")
3150 if self.description:
3151 self.write_indent(indent + 1, f"Description: {self.description}")
3152 self.write_indent(indent + 1, "Fields")
3153 for n_item in self.iter_sequence():
3154 n_item.dump(indent + 2)
3155 if self.checks:
3156 self.write_indent(indent + 1, "Checks")
3157 for n_check in self.checks:
3158 n_check.dump(indent + 2)
3159 else:
3160 self.write_indent(indent + 1, "Checks: None")
3162 def perform_type_checks(self, mh, value, gstab):
3163 # lobster-trace: LRM.Check_Evaluation_Order
3164 assert isinstance(mh, Message_Handler)
3165 assert isinstance(gstab, Symbol_Table)
3167 if isinstance(value, Tuple_Aggregate): 3167 ↛ 3174line 3167 didn't jump to line 3174 because the condition on line 3167 was always true
3168 ok = True
3169 for check in self.iter_checks():
3170 if not check.perform(mh, value, gstab):
3171 ok = False
3172 return ok
3173 else:
3174 assert isinstance(value, Implicit_Null)
3175 return True
3177 def get_example_value(self):
3178 # lobster-exclude: utility method
3179 parts = []
3180 for n_item in self.iter_sequence():
3181 if isinstance(n_item, Composite_Component):
3182 parts.append(n_item.n_typ.get_example_value())
3183 else:
3184 parts.append(n_item.to_string())
3185 if self.has_separators():
3186 return " ".join(parts)
3187 else:
3188 return "(%s)" % ", ".join(parts)
3191class Separator(Node):
3192 # lobster-trace: LRM.Tuple_Declaration
3193 """User-defined syntactic separator
3195 For example::
3197 separator x
3198 ^1
3200 :attribute token: token used to separate fields of the tuple
3201 :type: Token
3202 """
3204 def __init__(self, token):
3205 super().__init__(token.location)
3206 assert isinstance(token, Token) and token.kind in (
3207 "IDENTIFIER",
3208 "AT",
3209 "COLON",
3210 "SEMICOLON",
3211 )
3212 self.token = token
3214 def to_string(self):
3215 return {"AT": "@", "COLON": ":", "SEMICOLON": ";"}.get(
3216 self.token.kind, self.token.value
3217 )
3219 def dump(self, indent=0): # pragma: no cover
3220 self.write_indent(indent, f"Separator {self.token.value}")
3223class Enumeration_Type(Concrete_Type):
3224 """User-defined enumeration types.
3226 For example::
3228 enum T "potato" {
3229 ^1 ^2
3231 :attribute description: user supplied optional description, or None
3232 :type: str
3234 :attribute literals: the literals in this enumeration
3235 :type: Symbol_Table[Enumeration_Literal_Spec]
3237 """
3239 def __init__(self, name, description, location, package):
3240 # lobster-trace: LRM.Described_Name_Description
3241 super().__init__(name, location, package)
3242 assert isinstance(description, str) or description is None
3243 self.literals = Symbol_Table()
3244 self.description = description
3246 def dump(self, indent=0): # pragma: no cover
3247 # lobster-exclude: Debugging feature
3248 self.write_indent(indent, f"Enumeration_Type {self.name}")
3249 if self.description:
3250 self.write_indent(indent + 1, f"Description: {self.description}")
3251 self.literals.dump(indent + 1, omit_heading=True)
3253 def get_example_value(self):
3254 # lobster-exclude: utility method
3255 options = list(self.literals.values())
3256 if options:
3257 choice = len(options) // 2
3258 return self.name + "." + choice.name
3259 else:
3260 return "ERROR"
3263class Enumeration_Literal_Spec(Typed_Entity):
3264 """Declared literal in an enumeration declaration.
3266 Note that for literals mentioned later in record object
3267 declarations, we use :class:`Enumeration_Literal`. Literal specs
3268 are used here::
3270 enum ASIL {
3271 QM "not safety related"
3272 ^1 ^2
3274 :attribute description: the optional user-supplied description, or None
3275 :type: str
3277 """
3279 def __init__(self, name, description, location, enum):
3280 # lobster-trace: LRM.Described_Name_Description
3281 super().__init__(name, location, enum)
3282 assert isinstance(description, str) or description is None
3283 assert isinstance(enum, Enumeration_Type)
3284 self.description = description
3286 def dump(self, indent=0): # pragma: no cover
3287 # lobster-exclude: Debugging feature
3288 self.write_indent(indent, f"Enumeration_Literal_Spec {self.name}")
3289 if self.description:
3290 self.write_indent(indent + 1, f"Description: {self.description}")
3293class Record_Object(Typed_Entity):
3294 """A declared instance of a record type.
3296 This is going to be the bulk of all entities created by TRLC::
3298 section "Potato" {
3299 ^5
3300 Requirement PotatoReq {
3301 ^1 ^2
3302 component1 = 42
3303 ^3 ^4
3305 Note that the name (see 2) and type (see 1) of the object is
3306 provided by the name attribute of the :class:`Typed_Entity` base
3307 class.
3309 :attribute field: the specific values for all components (see 3 and 4)
3310 :type: dict[str, Expression]
3312 :attribute section: None or the section this record is contained in (see 5)
3313 :type: Section
3315 :attribute n_package: The package in which this record is declared in
3316 :type: Section
3318 The actual type of expressions in the field attribute are limited
3319 to:
3321 * :class:`Literal`
3322 * :class:`Unary_Expression`
3323 * :class:`Array_Aggregate`
3324 * :class:`Tuple_Aggregate`
3325 * :class:`Record_Reference`
3326 * :class:`Implicit_Null`
3328 """
3330 def __init__(self, name, location, n_typ, section, n_package):
3331 # lobster-trace: LRM.Section_Declaration
3332 # lobster-trace: LRM.Unspecified_Optional_Components
3333 # lobster-trace: LRM.Record_Object_Declaration
3335 assert isinstance(n_typ, Record_Type)
3336 assert isinstance(section, list) or section is None
3337 assert isinstance(n_package, Package)
3338 super().__init__(name, location, n_typ)
3339 self.field = {
3340 comp.name: Implicit_Null(self, comp) for comp in self.n_typ.all_components()
3341 }
3342 self.section = section
3343 self.n_package = n_package
3345 def fully_qualified_name(self):
3346 """Return the FQN for this type (i.e. PACKAGE.NAME)
3348 :returns: the object's full name
3349 :rtype: str
3350 """
3351 return self.n_package.name + "." + self.name
3353 def to_python_dict(self):
3354 """Return an evaluated and simplified object for Python.
3356 For example it might provide::
3358 {"foo" : [1, 2, 3],
3359 "bar" : None,
3360 "baz" : "value"}
3362 This is a function especially designed for the Python API. The
3363 name of the object itself is not in this returned dictionary.
3365 """
3366 return {name: value.to_python_object() for name, value in self.field.items()}
3368 def is_component_implicit_null(self, component) -> bool:
3369 return not isinstance(self.field[component.name], Implicit_Null)
3371 def assign(self, component, value):
3372 assert isinstance(component, Composite_Component)
3373 assert isinstance(
3374 value,
3375 (
3376 Literal,
3377 Array_Aggregate,
3378 Tuple_Aggregate,
3379 Record_Reference,
3380 Implicit_Null,
3381 Unary_Expression,
3382 ),
3383 ), "value is %s" % value.__class__.__name__
3384 if self.is_component_implicit_null(component): 3384 ↛ 3385line 3384 didn't jump to line 3385 because the condition on line 3384 was never true
3385 raise KeyError(
3386 f"Component {component.name} already \
3387 assigned to {self.n_typ.name} {self.name}!"
3388 )
3389 self.field[component.name] = value
3391 def dump(self, indent=0): # pragma: no cover
3392 # lobster-exclude: Debugging feature
3393 self.write_indent(indent, f"Record_Object {self.name}")
3394 self.write_indent(indent + 1, f"Type: {self.n_typ.name}")
3395 for key, value in self.field.items():
3396 self.write_indent(indent + 1, f"Field {key}")
3397 value.dump(indent + 2)
3398 if self.section:
3399 self.section[-1].dump(indent + 1)
3401 def resolve_references(self, mh):
3402 assert isinstance(mh, Message_Handler)
3403 for val in self.field.values():
3404 val.resolve_references(mh)
3406 def perform_checks(self, mh, gstab):
3407 # lobster-trace: LRM.Check_Evaluation_Order
3408 # lobster-trace: LRM.Evaluation_Of_Checks
3409 assert isinstance(mh, Message_Handler)
3410 assert isinstance(gstab, Symbol_Table)
3412 ok = True
3414 # First evaluate all tuple checks
3415 for n_comp in self.n_typ.all_components():
3416 if not n_comp.n_typ.perform_type_checks(mh, self.field[n_comp.name], gstab):
3417 ok = False
3419 # TODO: Is there a bug here (a check relies on a tuple check)?
3421 # Then evaluate all record checks
3422 for check in self.n_typ.iter_checks():
3423 # Prints messages, if applicable. Raises exception on
3424 # fatal checks, which causes this to abort.
3425 if not check.perform(mh, self, gstab):
3426 ok = False
3428 return ok
3430 def __repr__(self):
3431 return "%s<%s>" % (
3432 self.__class__.__name__,
3433 self.n_package.name + "." + self.n_typ.name + "." + self.name,
3434 )
3437class Section(Entity):
3438 # lobster-trace: LRM.Section_Declaration
3439 """A section for readability
3441 This represents a section construct in TRLC files to group record
3442 objects together::
3444 section "Foo" {
3445 ^^^^^ parent section
3446 section "Bar" {
3447 ^^^^^ section
3449 :attribute parent: the parent section or None
3450 :type: Section
3452 """
3454 def __init__(self, name, location, parent):
3455 super().__init__(name, location)
3456 assert isinstance(parent, Section) or parent is None
3457 self.parent = parent
3459 def dump(self, indent=0): # pragma: no cover
3460 self.write_indent(indent, f"Section {self.name}")
3461 if self.parent is None:
3462 self.write_indent(indent + 1, "Parent: None")
3463 else:
3464 self.write_indent(indent + 1, f"Parent: {self.parent.name}")
3467##############################################################################
3468# Symbol Table & Scopes
3469##############################################################################
3472class Symbol_Table:
3473 """Symbol table mapping names to entities"""
3475 def __init__(self, parent=None):
3476 # lobster-exclude: Constructor only declares variables
3477 assert isinstance(parent, Symbol_Table) or parent is None
3478 self.parent = parent
3479 self.imported = []
3480 self.table = OrderedDict()
3481 self.trlc_files = []
3482 self.section_names = []
3484 @staticmethod
3485 def simplified_name(name):
3486 # lobster-trace: LRM.Sufficiently_Distinct
3487 assert isinstance(name, str)
3488 return name.lower().replace("_", "")
3490 def all_names(self):
3491 # lobster-exclude: API for users
3492 """All names in the symbol table
3494 :rtype: set[str]
3495 """
3496 rv = set(item.name for item in self.table.values())
3497 if self.parent:
3498 rv |= self.parent.all_names()
3499 return rv
3501 def iter_record_objects_by_section(self):
3502 """API for users
3504 Retriving information about the section hierarchy for record objects
3505 Inputs: folder with trlc files where trlc files have sections,
3506 sub sections and record objects
3507 Output: Information about sections and level of sections,
3508 record objects and levels of record object
3509 """
3510 for record_object in self.iter_record_objects():
3511 location = record_object.location.file_name
3512 if location not in self.trlc_files: 3512 ↛ 3515line 3512 didn't jump to line 3515 because the condition on line 3512 was always true
3513 self.trlc_files.append(location)
3514 yield location
3515 if record_object.section:
3516 object_level = len(record_object.section) - 1
3517 for level, section in enumerate(record_object.section):
3518 if section not in self.section_names: 3518 ↛ 3517line 3518 didn't jump to line 3517 because the condition on line 3518 was always true
3519 self.section_names.append(section)
3520 yield section.name, level
3521 yield record_object, object_level
3522 else:
3523 object_level = 0
3524 yield record_object, object_level
3526 def iter_record_objects(self):
3527 # lobster-exclude: API for users
3528 """Iterate over all record objects
3530 :rtype: iterable[Record_Object]
3531 """
3532 for item in self.table.values():
3533 if isinstance(item, Package):
3534 yield from item.symbols.iter_record_objects()
3536 elif isinstance(item, Record_Object):
3537 yield item
3539 def values(self, subtype=None):
3540 # lobster-exclude: API for users
3541 assert subtype is None or isinstance(subtype, type)
3542 if self.parent:
3543 yield from self.parent.values(subtype)
3544 for name in sorted(self.table):
3545 if subtype is None or isinstance(self.table[name], subtype):
3546 yield self.table[name]
3548 def make_visible(self, stab):
3549 assert isinstance(stab, Symbol_Table)
3550 self.imported.append(stab)
3552 def register(self, mh, entity):
3553 # lobster-trace: LRM.Duplicate_Types
3554 # lobster-trace: LRM.Unique_Enumeration_Literals
3555 # lobster-trace: LRM.Tuple_Unique_Field_Names
3556 # lobster-trace: LRM.Sufficiently_Distinct
3557 # lobster-trace: LRM.Unique_Object_Names
3559 assert isinstance(mh, Message_Handler)
3560 assert isinstance(entity, Entity)
3562 simple_name = self.simplified_name(entity.name)
3564 if self.contains_raw(simple_name):
3565 pdef = self.lookup_direct(mh, entity.name, entity.location, simplified=True)
3566 if pdef.name == entity.name:
3567 mh.error(
3568 entity.location,
3569 "duplicate definition, previous definition at %s"
3570 % mh.cross_file_reference(pdef.location),
3571 )
3572 else:
3573 mh.error(
3574 entity.location,
3575 "%s is too similar to %s, declared at %s"
3576 % (entity.name, pdef.name, mh.cross_file_reference(pdef.location)),
3577 )
3579 else:
3580 self.table[simple_name] = entity
3582 def __contains__(self, name):
3583 # lobster-trace: LRM.Described_Name_Equality
3584 return self.contains(name)
3586 def contains_raw(self, simple_name, precise_name=None):
3587 # lobster-trace: LRM.Described_Name_Equality
3588 # lobster-trace: LRM.Sufficiently_Distinct
3589 #
3590 # Internal function to test if the simplified name is in the
3591 # table.
3592 assert isinstance(simple_name, str)
3593 assert isinstance(precise_name, str) or precise_name is None
3595 if simple_name in self.table:
3596 # No need to continue searching since registering a
3597 # clashing name would have been stopped
3598 return precise_name is None or self.table[simple_name].name == precise_name
3600 elif self.parent:
3601 return self.parent.contains_raw(simple_name, precise_name)
3603 for stab in self.imported:
3604 if stab.contains_raw(simple_name, precise_name):
3605 return True
3607 return False
3609 def contains(self, name):
3610 # lobster-trace: LRM.Described_Name_Equality
3611 """Tests if the given name is in the table
3613 :param name: the name to test
3614 :type name: str
3616 :rtype: bool
3617 """
3618 assert isinstance(name, str)
3619 return self.contains_raw(self.simplified_name(name), name)
3621 def lookup_assuming(self, mh, name, required_subclass=None):
3622 # lobster-trace: LRM.Described_Name_Equality
3623 # lobster-trace: LRM.Sufficiently_Distinct
3624 """Retrieve an object from the table assuming its there
3626 This is intended for the API specifically where you want to
3627 e.g. find some user-defined types you know are there.
3629 :param mh: The message handler to use
3630 :type mh: Message_Handler
3632 :param name: The name to search for
3633 :type name: str
3635 :param required_subclass: If set, creates an error if the object \
3636 is not an instance of the given class
3637 :type required_subclass: type
3639 :raise TRLC_Error: if the object is not of the required subclass
3640 :returns: the specified entity (or None if it does not exist)
3641 :rtype: Entity
3643 """
3644 assert isinstance(mh, Message_Handler)
3645 assert isinstance(name, str)
3646 assert isinstance(required_subclass, type) or required_subclass is None
3648 simple_name = self.simplified_name(name)
3650 ptr = self
3651 for ptr in [self] + self.imported: 3651 ↛ 3670line 3651 didn't jump to line 3670 because the loop on line 3651 didn't complete
3652 while ptr: 3652 ↛ 3651line 3652 didn't jump to line 3651 because the condition on line 3652 was always true
3653 if simple_name in ptr.table: 3653 ↛ 3668line 3653 didn't jump to line 3668 because the condition on line 3653 was always true
3654 rv = ptr.table[simple_name]
3655 if rv.name != name: 3655 ↛ 3656line 3655 didn't jump to line 3656 because the condition on line 3655 was never true
3656 return None
3658 if required_subclass is not None and not isinstance( 3658 ↛ 3661line 3658 didn't jump to line 3661 because the condition on line 3658 was never true
3659 rv, required_subclass
3660 ):
3661 mh.error(
3662 rv.location,
3663 "%s %s is not a %s"
3664 % (rv.__class__.__name__, name, required_subclass.__name__),
3665 )
3666 return rv
3667 else:
3668 ptr = ptr.parent
3670 return None
3672 def lookup_direct(
3673 self, mh, name, error_location, required_subclass=None, simplified=False
3674 ):
3675 # lobster-trace: LRM.Described_Name_Equality
3676 # lobster-trace: LRM.Sufficiently_Distinct
3677 # lobster-trace: LRM.Valid_Base_Names
3678 # lobster-trace: LRM.Valid_Access_Prefixes
3679 # lobster-trace: LRM.Valid_Function_Prefixes
3680 """Retrieve an object from the table
3682 For example::
3684 pkg = stab.lookup_direct(mh,
3685 "potato",
3686 Location("foobar.txt", 42),
3687 Package)
3689 This would search for an object named ``potato``. If it is
3690 found, and it is a package, it is returned. If it is not a
3691 Package, then the following error is issued::
3693 foobar.txt:42: error: Enumeration_Type potato is not a Package
3695 If it is not found at all, then the following error is issued::
3697 foobar.txt:42: error: unknown symbol potato
3699 :param mh: The message handler to use
3700 :type mh: Message_Handler
3702 :param name: The name to search for
3703 :type name: str
3705 :param error_location: Where to create the error if the name is \
3706 not found
3707 :type error_location: Location
3709 :param required_subclass: If set, creates an error if the object \
3710 is not an instance of the given class
3711 :type required_subclass: type
3713 :param simplified: If set, look up the given simplified name instead \
3714 of the actual name
3715 :type simplified: bool
3717 :raise TRLC_Error: if the name is not in the table
3718 :raise TRLC_Error: if the object is not of the required subclass
3719 :returns: the specified entity
3720 :rtype: Entity
3722 """
3723 assert isinstance(mh, Message_Handler)
3724 assert isinstance(name, str)
3725 assert isinstance(error_location, Location)
3726 assert isinstance(required_subclass, type) or required_subclass is None
3727 assert isinstance(simplified, bool)
3729 simple_name = self.simplified_name(name)
3730 ptr = self
3731 options = []
3733 for ptr in [self] + self.imported:
3734 while ptr:
3735 if simple_name in ptr.table:
3736 rv = ptr.table[simple_name]
3737 if not simplified and rv.name != name: 3737 ↛ 3738line 3737 didn't jump to line 3738 because the condition on line 3737 was never true
3738 mh.error(
3739 error_location,
3740 "unknown symbol %s, did you mean %s?" % (name, rv.name),
3741 )
3743 if required_subclass is not None and not isinstance(
3744 rv, required_subclass
3745 ):
3746 mh.error(
3747 error_location,
3748 "%s %s is not a %s"
3749 % (rv.__class__.__name__, name, required_subclass.__name__),
3750 )
3751 return rv
3752 else:
3753 options += list(item.name for item in ptr.table.values())
3754 ptr = ptr.parent
3756 matches = get_close_matches(word=name, possibilities=options, n=1)
3758 if matches:
3759 mh.error(
3760 error_location,
3761 "unknown symbol %s, did you mean %s?" % (name, matches[0]),
3762 )
3763 else:
3764 mh.error(error_location, "unknown symbol %s" % name)
3766 def lookup(self, mh, referencing_token, required_subclass=None):
3767 # lobster-trace: LRM.Described_Name_Equality
3768 assert isinstance(mh, Message_Handler)
3769 assert isinstance(referencing_token, Token)
3770 assert referencing_token.kind in ("IDENTIFIER", "BUILTIN")
3771 assert isinstance(required_subclass, type) or required_subclass is None
3773 return self.lookup_direct(
3774 mh=mh,
3775 name=referencing_token.value,
3776 error_location=referencing_token.location,
3777 required_subclass=required_subclass,
3778 )
3780 def write_indent(self, indent, message): # pragma: no cover
3781 # lobster-exclude: Debugging feature
3782 assert isinstance(indent, int)
3783 assert indent >= 0
3784 assert isinstance(message, str)
3785 print(" " * (3 * indent) + message)
3787 def dump(self, indent=0, omit_heading=False): # pragma: no cover
3788 # lobster-exclude: Debugging feature
3789 if omit_heading:
3790 new_indent = indent
3791 else:
3792 self.write_indent(indent, "Symbol_Table")
3793 new_indent = indent + 1
3794 ptr = self
3795 while ptr:
3796 for name in ptr.table:
3797 ptr.table[name].dump(new_indent)
3798 ptr = ptr.parent
3800 @classmethod
3801 def create_global_table(cls, mh):
3802 # lobster-trace: LRM.Builtin_Types
3803 # lobster-trace: LRM.Builtin_Functions
3804 # lobster-trace: LRM.Builtin_Type_Conversion_Functions
3805 # lobster-trace: LRM.Signature_Len
3806 # lobster-trace: LRM.Signature_String_End_Functions
3807 # lobster-trace: LRM.Signature_Matches
3809 stab = Symbol_Table()
3810 stab.register(mh, Builtin_Integer())
3811 stab.register(mh, Builtin_Decimal())
3812 stab.register(mh, Builtin_Boolean())
3813 stab.register(mh, Builtin_String())
3814 stab.register(mh, Builtin_Markup_String())
3815 stab.register(mh, Builtin_Function("len", 1))
3816 stab.register(mh, Builtin_Function("startswith", 2))
3817 stab.register(mh, Builtin_Function("endswith", 2))
3818 stab.register(mh, Builtin_Function("matches", 2))
3819 stab.register(mh, Builtin_Function("oneof", 1, arity_at_least=True))
3821 return stab
3824class Scope:
3825 def __init__(self):
3826 # lobster-exclude: Constructor only declares variables
3827 self.scope = []
3829 def push(self, stab):
3830 assert isinstance(stab, Symbol_Table)
3831 self.scope.append(stab)
3833 def pop(self):
3834 self.scope.pop()
3836 def contains(self, name):
3837 assert isinstance(name, str)
3839 for stab in reversed(self.scope):
3840 if stab.contains(name):
3841 return True
3842 return False
3844 def lookup(self, mh, referencing_token, required_subclass=None):
3845 assert len(self.scope) >= 1
3846 assert isinstance(mh, Message_Handler)
3847 assert isinstance(referencing_token, Token)
3848 assert referencing_token.kind in ("IDENTIFIER", "BUILTIN")
3849 assert isinstance(required_subclass, type) or required_subclass is None
3851 for stab in reversed(self.scope[1:]):
3852 if stab.contains(referencing_token.value):
3853 return stab.lookup(mh, referencing_token, required_subclass)
3854 return self.scope[0].lookup(mh, referencing_token, required_subclass)
3856 def size(self):
3857 return len(self.scope)