Coverage for trlc/ast.py: 90%
1199 statements
« prev ^ index » next coverage.py v7.10.6, created at 2025-09-05 17:22 +0000
« prev ^ index » next coverage.py v7.10.6, created at 2025-09-05 17:22 +0000
1#!/usr/bin/env python3
2#
3# TRLC - Treat Requirements Like Code
4# Copyright (C) 2022-2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
5# Copyright (C) 2024 Florian Schanda
6#
7# This file is part of the TRLC Python Reference Implementation.
8#
9# TRLC is free software: you can redistribute it and/or modify it
10# under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# TRLC is distributed in the hope that it will be useful, but WITHOUT
15# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
17# License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with TRLC. If not, see <https://www.gnu.org/licenses/>.
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##############################################################################
50class Value:
51 # lobster-trace: LRM.Boolean_Values
52 # lobster-trace: LRM.Integer_Values
53 # lobster-trace: LRM.Decimal_Values
54 # lobster-trace: LRM.String_Values
55 # lobster-trace: LRM.Markup_String_Values
56 """Polymorphic value for evaluating expressions.
58 Any record references will be fully resolved.
60 :attribute location: source location this value comes from
61 :type: Location
63 :attribute value: the value or None (for null values)
64 :type: str, int, bool, fractions.Fraction, list[Value], \
65 Record_Reference, Enumeration_Literal_Spec
67 :attribute typ: type of the value (or None for null values)
68 :type: Type
69 """
70 def __init__(self, location, value, typ):
71 assert isinstance(location, Location)
72 assert value is None or \
73 isinstance(value, (str,
74 int,
75 bool,
76 list, # for arrays
77 dict, # for tuples
78 Fraction,
79 Record_Reference,
80 Enumeration_Literal_Spec))
81 assert typ is None or isinstance(typ, Type)
82 assert (typ is None) == (value is None)
84 self.location = location
85 self.value = value
86 self.typ = typ
88 def __eq__(self, other):
89 return self.typ == other.typ and self.value == other.value
91 def __repr__(self): # pragma: no cover
92 return "Value(%s)" % self.value
94 def resolve_references(self, mh):
95 assert isinstance(mh, Message_Handler)
97 if isinstance(self.value, Record_Reference):
98 self.value.resolve(mh)
101##############################################################################
102# AST Nodes
103##############################################################################
105class Node(metaclass=ABCMeta):
106 """Base class for all AST items.
108 :attribute location: source location
109 :type: Location
110 """
111 def __init__(self, location):
112 # lobster-exclude: Constructor only declares variables
113 assert isinstance(location, Location)
114 self.location = location
116 def set_ast_link(self, tok):
117 assert isinstance(tok, Token)
118 tok.ast_link = self
120 def write_indent(self, indent, message): # pragma: no cover
121 # lobster-exclude: Debugging feature
122 assert isinstance(indent, int)
123 assert indent >= 0
124 assert isinstance(message, str)
125 print(" " * (3 * indent) + message)
127 @abstractmethod
128 def dump(self, indent=0): # pragma: no cover
129 """Visualise the parse tree.
131 This can be called for any :class:`Node` or
132 :class:`Symbol_Table`, and can be very helpful for debugging
133 or understanding the parse tree. The dump methods will produce
134 output like this::
136 Symbol_Table
137 Builtin_Boolean
138 Builtin_Integer
139 Builtin_Decimal
140 Builtin_String
141 Builtin_Markup_String
142 Package bar
143 Symbol_Table
144 Record_Type MyType
145 Composite_Component name
146 Optional: False
147 Type: String
148 Checks
149 Error 'description is too short'
150 Anchor: description
151 Binary Binary_Operator.COMP_GT Expression
152 Type: Boolean
153 Unary Unary_Operator.STRING_LENGTH Expression
154 Type: Integer
155 Name Reference to description
156 Integer Literal 10
157 Package instances
158 Symbol_Table
159 Record_Object SomeThing
160 Type: MyType
161 Field description: "Potato"
162 Builtin_Function endswith
163 Builtin_Function len
164 Builtin_Function matches
165 Builtin_Function startswith
166 Builtin_Function oneof
168 """
169 assert isinstance(indent, int) and indent >= 0
170 assert False, f"dump not implemented for {self.__class__.__name__}"
171 # lobster-exclude: Debugging feature
174class Check_Block(Node):
175 """Node representing check blocks
177 Semantically this has no meaning, but it's nice to have some kind
178 of similar representation to how it's in the file.
180 :attribute n_typ: composite type for which the checks apply
181 :type: Composite_Type
183 :attribute checks: list of checks
184 :type: list[Check]
186 """
187 def __init__(self, location, n_typ):
188 # lobster-trace: LRM.Check_Block
189 super().__init__(location)
190 assert isinstance(n_typ, Composite_Type)
191 self.n_typ = n_typ
192 self.checks = []
194 def add_check(self, n_check):
195 # lobster-trace: LRM.Check_Evaluation_Order
196 assert isinstance(n_check, Check)
197 self.checks.append(n_check)
199 def dump(self, indent=0): # pragma: no cover
200 # lobster-exclude: Debugging feature
201 self.write_indent(indent, "Check_Block")
202 self.write_indent(indent + 1, f"Type: {self.n_typ.name}")
203 for n_check in self.checks:
204 n_check.dump(indent + 1)
207class Compilation_Unit(Node):
208 """Special node to represent the concrete file structure
210 :attribute package: the main package this file declares or contributes to
211 :type: Package
213 :attribute imports: package imported by this file
214 :type: list[Package]
216 :attribute items: list of
217 :type: list[Node]
219 """
220 def __init__(self, file_name):
221 # lobster-exclude: Constructor only declares variables
222 super().__init__(Location(file_name))
223 self.package = None
224 self.imports = None
225 self.raw_imports = []
226 self.items = []
228 def dump(self, indent=0): # pragma: no cover
229 # lobster-exclude: Debugging feature
230 self.write_indent(indent, f"Compilation_Unit ({self.location.file_name})")
231 for t_import in self.raw_imports:
232 self.write_indent(indent + 1, f"Import: {t_import.value}")
233 for n_item in self.items:
234 n_item.dump(indent + 1)
236 def set_package(self, pkg):
237 # lobster-trace: LRM.Current_Package
238 assert isinstance(pkg, Package)
239 self.package = pkg
241 def add_import(self, mh, t_import):
242 # lobster-trace: LRM.Import_Visibility
243 # lobster-trace: LRM.Self_Imports
244 assert isinstance(mh, Message_Handler)
245 assert isinstance(t_import, Token)
246 assert t_import.kind == "IDENTIFIER"
248 if t_import.value == self.package.name:
249 mh.error(t_import.location,
250 "package %s cannot import itself" % self.package.name)
252 # Skip duplicates
253 for t_previous in self.raw_imports:
254 if t_previous.value == t_import.value:
255 mh.warning(t_import.location,
256 "duplicate import of package %s" % t_import.value)
257 return
259 self.raw_imports.append(t_import)
261 def resolve_imports(self, mh, stab):
262 # lobster-trace: LRM.Import_Visibility
263 assert isinstance(mh, Message_Handler)
264 assert isinstance(stab, Symbol_Table)
265 self.imports = set()
266 for t_import in self.raw_imports:
267 # We can ignore errors here, because that just means we
268 # generate more error later.
269 try:
270 a_import = stab.lookup(mh, t_import, Package)
271 self.imports.add(a_import)
272 a_import.set_ast_link(t_import)
273 except TRLC_Error:
274 pass
276 def is_visible(self, n_pkg):
277 # lobster-trace: LRM.Import_Visibility
278 assert self.imports is not None
279 assert isinstance(n_pkg, Package)
280 return n_pkg == self.package or n_pkg in self.imports
282 def add_item(self, node):
283 # lobster-trace: LRM.RSL_File
284 # lobster-trace: LRM.TRLC_File
285 assert isinstance(node, (Concrete_Type,
286 Check_Block,
287 Record_Object)), \
288 "trying to add %s to a compilation unit" % node.__class__.__name__
289 self.items.append(node)
292class Check(Node):
293 """User defined check
295 This represent a single user-defined check inside a check block::
297 checks T {
298 a /= null implies a > 5, warning "potato", a
299 ^^^^^^^^^^^^^^^^^^^^^^^1 ^2 ^3 ^4
301 :attribute n_type: The tuple/record type this check applies to
302 :type: Composite_Type
304 :attribute n_expr: The boolean expression for the check (see 1)
305 :type: Expression
307 :attribute n_anchor: The (optional) record component where the message \
308 should be issued (or None) (see 4)
309 :type: Composite_Component
311 :attribute severity: warning, error, or fatal (see 2; also if this is \
312 not specified the default is 'error')
313 :type: str
315 :attribute message: the user-supplied message (see 3)
316 :type: str
317 """
318 def __init__(self,
319 n_type,
320 n_expr,
321 n_anchor,
322 severity,
323 t_message,
324 extrainfo):
325 # lobster-trace: LRM.Check_Block
326 assert isinstance(n_type, Composite_Type)
327 assert isinstance(n_expr, Expression)
328 assert isinstance(n_anchor, Composite_Component) or n_anchor is None
329 assert severity in ("warning", "error", "fatal")
330 assert isinstance(t_message, Token)
331 assert t_message.kind == "STRING"
332 assert isinstance(extrainfo, str) or extrainfo is None
333 super().__init__(t_message.location)
335 self.n_type = n_type
336 self.n_expr = n_expr
337 self.n_anchor = n_anchor
338 self.severity = severity
339 # lobster-trace: LRM.No_Newlines_In_Message
340 # This is the error recovery strategy if we find newlines in
341 # the short error messages: we just remove them. The error
342 # raised is non-fatal.
343 self.message = t_message.value.replace("\n", " ")
344 self.extrainfo = extrainfo
346 def dump(self, indent=0): # pragma: no cover
347 # lobster-exclude: Debugging feature
348 if self.severity == "warning":
349 self.write_indent(indent, f"Warning '{self.message}'")
350 elif self.severity == "error":
351 self.write_indent(indent, f"Error '{self.message}'")
352 else:
353 self.write_indent(indent, f"Fatal error '{self.message}'")
354 if self.n_anchor:
355 self.write_indent(indent + 1, f"Anchor: {self.n_anchor.name}")
356 self.n_expr.dump(indent + 1)
358 def get_real_location(self, composite_object):
359 # lobster-exclude: LRM.Anchoring
360 assert isinstance(composite_object, (Record_Object,
361 Tuple_Aggregate))
362 if isinstance(composite_object, Record_Object):
363 fields = composite_object.field
364 else:
365 fields = composite_object.value
367 if self.n_anchor is None or fields[self.n_anchor.name] is None:
368 return composite_object.location
369 else:
370 return fields[self.n_anchor.name].location
372 def perform(self, mh, composite_object):
373 # lobster-trace: LRM.Check_Messages
374 # lobster-trace: LRM.Check_Severity
375 assert isinstance(mh, Message_Handler)
376 assert isinstance(composite_object, (Record_Object,
377 Tuple_Aggregate))
379 if isinstance(composite_object, Record_Object):
380 result = self.n_expr.evaluate(mh, copy(composite_object.field))
381 else:
382 result = self.n_expr.evaluate(mh, copy(composite_object.value))
383 assert isinstance(result.value, bool)
385 if not result.value:
386 loc = self.get_real_location(composite_object)
387 if self.severity == "warning":
388 mh.warning(location = loc,
389 message = self.message,
390 explanation = self.extrainfo,
391 user = True)
392 else:
393 mh.error(location = loc,
394 message = self.message,
395 explanation = self.extrainfo,
396 fatal = self.severity == "fatal",
397 user = True)
398 return False
400 return True
402##############################################################################
403# AST Nodes (Expressions)
404##############################################################################
407class Unary_Operator(Enum):
408 # lobster-exclude: Utility enumeration for unary operators
409 MINUS = auto()
410 PLUS = auto()
411 LOGICAL_NOT = auto()
412 ABSOLUTE_VALUE = auto()
414 STRING_LENGTH = auto()
415 ARRAY_LENGTH = auto()
417 CONVERSION_TO_INT = auto()
418 CONVERSION_TO_DECIMAL = auto()
421class Binary_Operator(Enum):
422 # lobster-exclude: Utility enumeration for binary operators
423 LOGICAL_AND = auto() # Short-circuit
424 LOGICAL_OR = auto() # Short-circuit
425 LOGICAL_XOR = auto()
426 LOGICAL_IMPLIES = auto() # Short-circuit
428 COMP_EQ = auto()
429 COMP_NEQ = auto()
430 COMP_LT = auto()
431 COMP_LEQ = auto()
432 COMP_GT = auto()
433 COMP_GEQ = auto()
435 STRING_CONTAINS = auto()
436 STRING_STARTSWITH = auto()
437 STRING_ENDSWITH = auto()
438 STRING_REGEX = auto()
440 ARRAY_CONTAINS = auto()
442 PLUS = auto()
443 MINUS = auto()
444 TIMES = auto()
445 DIVIDE = auto()
446 REMAINDER = auto()
448 POWER = auto()
450 INDEX = auto()
453class Expression(Node, metaclass=ABCMeta):
454 """Abstract base class for all expressions.
456 :attribute typ: The type of this expression (or None for null values)
457 :type: Type
458 """
459 def __init__(self, location, typ):
460 # lobster-exclude: Constructor only declares variables
461 super().__init__(location)
462 assert typ is None or isinstance(typ, Type)
463 self.typ = typ
465 def evaluate(self, mh, context): # pragma: no cover
466 """Evaluate the expression in the given context
468 The context can be None, in which case the expression is
469 evaluated in a static context. Otherwise it must be a
470 dictionary that maps names (such as record fields or
471 quantified variables) to expressions.
473 :param mh: the message handler to use
474 :type mh: Message_Handler
475 :param context: name mapping or None (for a static context)
476 :type context: dict[str, Expression]
477 :raise TRLC_Error: if the expression cannot be evaluated
478 :return: result of the evaluation
479 :rtype: Value
480 """
481 assert isinstance(mh, Message_Handler)
482 assert context is None or isinstance(context, dict)
483 assert False, "evaluate not implemented for %s" % \
484 self.__class__.__name__
486 @abstractmethod
487 def to_string(self): # pragma: no cover
488 assert False, "to_string not implemented for %s" % \
489 self.__class__.__name__
491 def ensure_type(self, mh, typ):
492 # lobster-trace: LRM.Restricted_Null
493 # lobster-trace: LRM.Null_Is_Invalid
495 assert isinstance(typ, (type, Type))
496 if self.typ is None:
497 mh.error(self.location,
498 "null is not permitted here")
499 elif isinstance(typ, type) and not isinstance(self.typ, typ):
500 mh.error(self.location,
501 "expected expression of type %s, got %s instead" %
502 (typ.__name__,
503 self.typ.__class__.__name__))
504 elif isinstance(typ, Type) and self.typ != typ:
505 mh.error(self.location,
506 "expected expression of type %s, got %s instead" %
507 (typ.name,
508 self.typ.name))
510 def resolve_references(self, mh):
511 assert isinstance(mh, Message_Handler)
513 @abstractmethod
514 def can_be_null(self):
515 """Test if the expression could return null
517 Checks the expression if it could generate a null value
518 *without* raising an error. For example `x` could generate a
519 null value if `x` is a record component that is
520 optional. However `x + 1` could not, since an error would
521 occur earlier.
523 :return: possibility of encountering null
524 :rtype: bool
526 """
527 assert False, "can_be_null not implemented for %s" % \
528 self.__class__.__name__
531class Implicit_Null(Expression):
532 """Synthesised null values
534 When a record object or tuple aggregate is declared and an
535 optional component or field is not specified, we synthesise an
536 implicit null expression for this.
538 For example given this TRLC type::
540 type T {
541 x optional Integer
542 }
544 And this declaration::
546 T Potato {}
548 Then the field mapping for Potato will be::
550 {x: Implicit_Null}
552 Each field will get its own implicit null. Further note that this
553 implicit null is distinct from the explicit :class:`Null_Literal`
554 that can appear in check expressions.
556 """
557 def __init__(self, composite_object, composite_component):
558 # lobster-trace: LRM.Unspecified_Optional_Components
559 assert isinstance(composite_object, (Record_Object,
560 Tuple_Aggregate))
561 assert isinstance(composite_component, Composite_Component)
562 super().__init__(composite_object.location, None)
564 def to_string(self):
565 return "null"
567 def evaluate(self, mh, context):
568 # lobster-trace: LRM.Unspecified_Optional_Components
569 assert isinstance(mh, Message_Handler)
570 assert context is None or isinstance(context, dict)
571 return Value(self.location, None, None)
573 def to_python_object(self):
574 return None
576 def dump(self, indent=0): # pragma: no cover
577 # lobster-exclude: Debugging feature
578 self.write_indent(indent, "Implicit_Null")
580 def can_be_null(self):
581 return True
584class Literal(Expression, metaclass=ABCMeta):
585 """Abstract base for all Literals
587 Does not offer any additional features, but it's a nice way to
588 group together all literal types. This is useful if you want to
589 check if you are dealing with a literal::
591 isinstance(my_expression, Literal)
593 """
594 @abstractmethod
595 def to_python_object(self):
596 assert False
599class Null_Literal(Literal):
600 # lobster-trace: LRM.Primary
601 """The null literal
603 This can appear in check expressions::
605 a /= null implies a > 5
606 ^^^^
608 Please note that this is distinct from the :class:`Implicit_Null`
609 values that appear in record objects.
611 """
612 def __init__(self, token):
613 assert isinstance(token, Token)
614 assert token.kind == "KEYWORD"
615 assert token.value == "null"
616 super().__init__(token.location, None)
618 def dump(self, indent=0): # pragma: no cover
619 self.write_indent(indent, "Null Literal")
621 def to_string(self):
622 return "null"
624 def evaluate(self, mh, context):
625 assert isinstance(mh, Message_Handler)
626 assert context is None or isinstance(context, dict)
627 return Value(self.location, None, None)
629 def to_python_object(self):
630 return None
632 def can_be_null(self):
633 return True
636class Integer_Literal(Literal):
637 # lobster-trace: LRM.Integer_Values
638 # lobster-trace: LRM.Primary
639 """Integer literals
641 Note that these are always positive. A negative integer is
642 actually a unary negation expression, operating on a positive
643 integer literal::
645 x == -5
647 This would create the following tree::
649 OP_EQUALITY
650 NAME_REFERENCE x
651 UNARY_EXPRESSION -
652 INTEGER_LITERAL 5
654 :attribute value: the non-negative integer value
655 :type: int
656 """
657 def __init__(self, token, typ):
658 assert isinstance(token, Token)
659 assert token.kind == "INTEGER"
660 assert isinstance(typ, Builtin_Integer)
661 super().__init__(token.location, typ)
663 self.value = token.value
665 def dump(self, indent=0): # pragma: no cover
666 self.write_indent(indent, f"Integer Literal {self.value}")
668 def to_string(self):
669 return str(self.value)
671 def evaluate(self, mh, context):
672 assert isinstance(mh, Message_Handler)
673 assert context is None or isinstance(context, dict)
674 return Value(self.location, self.value, self.typ)
676 def to_python_object(self):
677 return self.value
679 def can_be_null(self):
680 return False
683class Decimal_Literal(Literal):
684 # lobster-trace: LRM.Decimal_Values
685 # lobster-trace: LRM.Primary
686 """Decimal literals
688 Note that these are always positive. A negative decimal is
689 actually a unary negation expression, operating on a positive
690 decimal literal::
692 x == -5.0
694 This would create the following tree::
696 OP_EQUALITY
697 NAME_REFERENCE x
698 UNARY_EXPRESSION -
699 DECIMAL_LITERAL 5.0
701 :attribute value: the non-negative decimal value
702 :type: fractions.Fraction
703 """
704 def __init__(self, token, typ):
705 assert isinstance(token, Token)
706 assert token.kind == "DECIMAL"
707 assert isinstance(typ, Builtin_Decimal)
708 super().__init__(token.location, typ)
710 self.value = token.value
712 def dump(self, indent=0): # pragma: no cover
713 self.write_indent(indent, f"Decimal Literal {self.value}")
715 def to_string(self):
716 return str(self.value)
718 def evaluate(self, mh, context):
719 assert isinstance(mh, Message_Handler)
720 assert context is None or isinstance(context, dict)
721 return Value(self.location, self.value, self.typ)
723 def to_python_object(self):
724 return float(self.value)
726 def can_be_null(self):
727 return False
730class String_Literal(Literal):
731 # lobster-trace: LRM.String_Values
732 # lobster-trace: LRM.Markup_String_Values
733 # lobster-trace: LRM.Primary
734 """String literals
736 Note the value of the string does not include the quotation marks,
737 and any escape sequences are fully resolved. For example::
739 "foo\\"bar"
741 Will have a value of ``foo"bar``.
743 :attribute value: string content
744 :type: str
746 :attribute references: resolved references of a markup string
747 :type: list[Record_Reference]
749 """
750 def __init__(self, token, typ):
751 assert isinstance(token, Token)
752 assert token.kind == "STRING"
753 assert isinstance(typ, Builtin_String)
754 super().__init__(token.location, typ)
756 self.value = token.value
757 self.has_references = isinstance(typ, Builtin_Markup_String)
758 self.references = []
760 def dump(self, indent=0): # pragma: no cover
761 self.write_indent(indent, f"String Literal {repr(self.value)}")
762 if self.has_references:
763 self.write_indent(indent + 1, "Markup References")
764 for ref in self.references:
765 ref.dump(indent + 2)
767 def to_string(self):
768 return self.value
770 def evaluate(self, mh, context):
771 assert isinstance(mh, Message_Handler)
772 assert context is None or isinstance(context, dict)
773 return Value(self.location, self.value, self.typ)
775 def to_python_object(self):
776 return self.value
778 def resolve_references(self, mh):
779 assert isinstance(mh, Message_Handler)
780 for ref in self.references:
781 ref.resolve_references(mh)
783 def can_be_null(self):
784 return False
787class Boolean_Literal(Literal):
788 # lobster-trace: LRM.Boolean_Values
789 # lobster-trace: LRM.Primary
790 """Boolean values
792 :attribute value: the boolean value
793 :type: bool
794 """
795 def __init__(self, token, typ):
796 assert isinstance(token, Token)
797 assert token.kind == "KEYWORD"
798 assert token.value in ("false", "true")
799 assert isinstance(typ, Builtin_Boolean)
800 super().__init__(token.location, typ)
802 self.value = token.value == "true"
804 def dump(self, indent=0): # pragma: no cover
805 self.write_indent(indent, f"Boolean Literal {self.value}")
807 def to_string(self):
808 return str(self.value)
810 def evaluate(self, mh, context):
811 assert isinstance(mh, Message_Handler)
812 assert context is None or isinstance(context, dict)
813 return Value(self.location, self.value, self.typ)
815 def to_python_object(self):
816 return self.value
818 def can_be_null(self):
819 return False
822class Enumeration_Literal(Literal):
823 """Enumeration values
825 Note that this is distinct from
826 :class:`Enumeration_Literal_Spec`. An enumeration literal is a
827 specific mention of an enumeration member in an expression::
829 foo != my_enum.POTATO
830 ^^^^^^^^^^^^^^
832 To get to the string value of the enumeration literal
833 (i.e. ``POTATO`` here) you can get the name of the literal spec
834 itself: ``enum_lit.value.name``; and to get the name of the
835 enumeration (i.e. ``my_enum`` here) you can use
836 ``enum_lit.value.n_typ.name``.
838 :attribute value: enumeration value
839 :type: Enumeration_Literal_Spec
841 """
842 def __init__(self, location, literal):
843 # lobster-exclude: Constructor only declares variables
844 assert isinstance(literal, Enumeration_Literal_Spec)
845 super().__init__(location, literal.n_typ)
847 self.value = literal
849 def dump(self, indent=0): # pragma: no cover
850 # lobster-exclude: Debugging feature
851 self.write_indent(indent,
852 f"Enumeration Literal {self.typ.name}.{self.value.name}")
854 def to_string(self):
855 return self.typ.name + "." + self.value.name
857 def evaluate(self, mh, context):
858 assert isinstance(mh, Message_Handler)
859 assert context is None or isinstance(context, dict)
860 return Value(self.location, self.value, self.typ)
862 def to_python_object(self):
863 return self.value.name
865 def can_be_null(self):
866 return False
869class Array_Aggregate(Expression):
870 """Instances of array types
872 This is created when assigning to array components::
874 potatoes = ["picasso", "yukon gold", "sweet"]
875 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
877 The type of expression that can be found in an array is somewhat
878 limited:
880 * :class:`Literal`
881 * :class:`Array_Aggregate`
882 * :class:`Record_Reference`
884 :attribute value: contents of the array
885 :type: list[Expression]
887 """
888 def __init__(self, location, typ):
889 # lobster-trace: LRM.Record_Object_Declaration
891 super().__init__(location, typ)
892 self.value = []
894 def dump(self, indent=0): # pragma: no cover
895 # lobster-exclude: Debugging feature
896 self.write_indent(indent, "Array_Aggregate")
897 for n_value in self.value:
898 n_value.dump(indent + 1)
900 def append(self, value):
901 assert isinstance(value, (Literal,
902 Unary_Expression,
903 Array_Aggregate,
904 Tuple_Aggregate,
905 Record_Reference))
906 self.value.append(value)
908 def to_string(self):
909 return "[" + ", ".join(x.to_string() for x in self.value) + "]"
911 def evaluate(self, mh, context):
912 assert isinstance(mh, Message_Handler)
913 assert context is None or isinstance(context, dict)
914 return Value(self.location,
915 list(element.evaluate(mh, context)
916 for element in self.value),
917 self.typ)
919 def resolve_references(self, mh):
920 assert isinstance(mh, Message_Handler)
922 for val in self.value:
923 val.resolve_references(mh)
925 def to_python_object(self):
926 return [x.to_python_object() for x in self.value]
928 def can_be_null(self):
929 return False
932class Tuple_Aggregate(Expression):
933 """Instances of a tuple
935 This is created when assigning to a tuple components. There are
936 two forms, the ordinary form::
938 coordinate = (12.3, 40.0)
939 ^^^^^^^^^^^^
941 And the separator form::
943 item = 12345@42
944 ^^^^^^^^
946 In terms of AST there is no difference, as the separator is only
947 syntactic sugar.
949 :attribute value: contents of the tuple
950 :type: dict[str, Expression]
952 """
953 def __init__(self, location, typ):
954 # lobster-trace: LRM.Unspecified_Optional_Components
955 # lobster-trace: LRM.Record_Object_Declaration
957 super().__init__(location, typ)
958 self.value = {n_field.name : Implicit_Null(self, n_field)
959 for n_field in self.typ.components.values()}
961 def assign(self, field, value):
962 assert isinstance(field, str)
963 assert isinstance(value, (Literal,
964 Unary_Expression,
965 Tuple_Aggregate,
966 Record_Reference)), \
967 "value is %s" % value.__class__.__name__
968 assert field in self.typ.components
970 self.value[field] = value
972 def dump(self, indent=0): # pragma: no cover
973 # lobster-exclude: Debugging feature
974 self.write_indent(indent, "Tuple_Aggregate")
975 self.write_indent(indent + 1, f"Type: {self.typ.name}")
976 for n_item in self.typ.iter_sequence():
977 if isinstance(n_item, Composite_Component):
978 self.value[n_item.name].dump(indent + 1)
980 def to_string(self):
981 first = True
982 if self.typ.has_separators():
983 rv = ""
984 else:
985 rv = "("
986 for n_item in self.typ.iter_sequence():
987 if isinstance(n_item, Separator):
988 rv += " %s " % n_item.token.value
989 elif first:
990 first = False
991 else:
992 rv += ", "
994 if isinstance(n_item, Composite_Component):
995 rv += self.value[n_item.name].to_string()
996 if self.typ.has_separators():
997 rv = ""
998 else:
999 rv = ")"
1000 return rv
1002 def evaluate(self, mh, context):
1003 assert isinstance(mh, Message_Handler)
1004 assert context is None or isinstance(context, dict)
1005 return Value(self.location,
1006 {name : element.evaluate(mh, context)
1007 for name, element in self.value.items()},
1008 self.typ)
1010 def resolve_references(self, mh):
1011 assert isinstance(mh, Message_Handler)
1013 for val in self.value.values():
1014 val.resolve_references(mh)
1016 def to_python_object(self):
1017 return {name: value.to_python_object()
1018 for name, value in self.value.items()}
1020 def can_be_null(self):
1021 return False
1024class Record_Reference(Expression):
1025 """Reference to another record object
1027 This can appear in record object declarations::
1029 Requirement Kitten {
1030 depends_on = Other_Package.Cat
1031 ^1 ^2
1032 }
1034 Note that this is distinct from :class:`Record_Object`. It is just
1035 the name; to get to the object referred to by this you can consult
1036 the target attribute.
1038 The reason we have this indirection is that not all names can be
1039 immediately resolved on parsing in the TRLC language.
1041 Note that while the containing package (see 1) is optional in the
1042 source language, the containing package will always be filled in
1043 in this AST node.
1045 :attribute name: The name of the record (see 2)
1046 :type: str
1048 :attribute target: The concrete record object referred to by (2)
1049 :type: Record_Object
1051 :attribute package: The package (see 1) supposed to contain (2)
1052 :type: Package
1054 """
1055 def __init__(self, location, name, typ, package):
1056 # lobster-exclude: Constructor only declares variables
1057 assert isinstance(location, Location)
1058 assert isinstance(name, str)
1059 assert isinstance(typ, Record_Type) or typ is None
1060 assert isinstance(package, Package)
1061 super().__init__(location, typ)
1063 self.name = name
1064 self.target = None
1065 self.package = package
1067 def dump(self, indent=0): # pragma: no cover
1068 # lobster-exclude: Debugging feature
1069 self.write_indent(indent, f"Record Reference {self.name}")
1070 self.write_indent(indent + 1,
1071 f"Resolved: {self.target is not None}")
1073 def to_string(self):
1074 return self.name
1076 def evaluate(self, mh, context):
1077 assert isinstance(mh, Message_Handler)
1078 assert context is None or isinstance(context, dict)
1079 return Value(self.location, self, self.typ)
1081 def resolve_references(self, mh):
1082 # lobster-trace: LRM.References_To_Extensions
1083 assert isinstance(mh, Message_Handler)
1085 self.target = self.package.symbols.lookup_direct(
1086 mh = mh,
1087 name = self.name,
1088 error_location = self.location,
1089 required_subclass = Record_Object)
1090 if self.typ is None:
1091 self.typ = self.target.n_typ
1092 elif not self.target.n_typ.is_subclass_of(self.typ):
1093 mh.error(self.location,
1094 "expected reference of type %s, but %s is of type %s" %
1095 (self.typ.name,
1096 self.target.name,
1097 self.target.n_typ.name))
1099 def to_python_object(self):
1100 return self.target.fully_qualified_name()
1102 def can_be_null(self):
1103 return False
1106class Name_Reference(Expression):
1107 # lobster-trace: LRM.Qualified_Name
1108 # lobster-trace: LRM.Static_Regular_Expression
1110 """Reference to a name
1112 Name reference to either a :class:`Composite_Component` or a
1113 :class:`Quantified_Variable`. The actual value of course depends
1114 on the context. See :py:meth:`Expression.evaluate()`.
1116 For example::
1118 (forall x in potato => x > 1)
1119 ^1 ^2
1121 Both indicated parts are a :class:`Name_Reference`, the first one
1122 refers to a :class:`Composite_Component`, and the second refers to a
1123 :class:`Quantified_Variable`.
1125 :attribute entity: the entity named here
1126 :type: Composite_Component, Quantified_Variable
1127 """
1128 def __init__(self, location, entity):
1129 assert isinstance(entity, (Composite_Component,
1130 Quantified_Variable))
1131 super().__init__(location, entity.n_typ)
1132 self.entity = entity
1134 def dump(self, indent=0): # pragma: no cover
1135 self.write_indent(indent, f"Name Reference to {self.entity.name}")
1137 def to_string(self):
1138 return self.entity.name
1140 def evaluate(self, mh, context):
1141 assert isinstance(mh, Message_Handler)
1142 assert context is None or isinstance(context, dict)
1144 if context is None:
1145 mh.error(self.location,
1146 "cannot be used in a static context")
1148 assert self.entity.name in context
1149 return context[self.entity.name].evaluate(mh, context)
1151 def can_be_null(self):
1152 # The only way we could generate null here (without raising
1153 # error earlier) is when we refer to a component that is
1154 # optional.
1155 if isinstance(self.entity, Composite_Component):
1156 return self.entity.optional
1157 else:
1158 return False
1161class Unary_Expression(Expression):
1162 """Expression with only one operand
1164 This captures the following operations:
1166 * Unary_Operator.PLUS (e.g. ``+5``)
1167 * Unary_Operator.MINUS (e.g. ``-5``)
1168 * Unary_Operator.ABSOLUTE_VALUE (e.g. ``abs 42``)
1169 * Unary_Operator.LOGICAL_NOT (e.g. ``not True``)
1170 * Unary_Operator.STRING_LENGTH (e.g. ``len("foobar")``)
1171 * Unary_Operator.ARRAY_LENGTH (e.g. ``len(component_name)``)
1172 * Unary_Operator.CONVERSION_TO_INT (e.g. ``Integer(5.3)``)
1173 * Unary_Operator.CONVERSION_TO_DECIMAL (e.g. ``Decimal(5)``)
1175 Note that several builtin functions are mapped to unary operators.
1177 :attribute operator: the operation
1178 :type: Unary_Operator
1180 :attribute n_operand: the expression we operate on
1181 :type: Expression
1183 """
1184 def __init__(self, mh, location, typ, operator, n_operand):
1185 # lobster-trace: LRM.Simple_Expression
1186 # lobster-trace: LRM.Relation
1187 # lobster-trace: LRM.Factor
1188 # lobster-trace: LRM.Signature_Len
1189 # lobster-trace: LRM.Signature_Type_Conversion
1191 super().__init__(location, typ)
1192 assert isinstance(mh, Message_Handler)
1193 assert isinstance(operator, Unary_Operator)
1194 assert isinstance(n_operand, Expression)
1195 self.operator = operator
1196 self.n_operand = n_operand
1198 if operator in (Unary_Operator.MINUS,
1199 Unary_Operator.PLUS,
1200 Unary_Operator.ABSOLUTE_VALUE):
1201 self.n_operand.ensure_type(mh, Builtin_Numeric_Type)
1202 elif operator == Unary_Operator.LOGICAL_NOT:
1203 self.n_operand.ensure_type(mh, Builtin_Boolean)
1204 elif operator == Unary_Operator.STRING_LENGTH:
1205 self.n_operand.ensure_type(mh, Builtin_String)
1206 elif operator == Unary_Operator.ARRAY_LENGTH:
1207 self.n_operand.ensure_type(mh, Array_Type)
1208 elif operator == Unary_Operator.CONVERSION_TO_INT:
1209 self.n_operand.ensure_type(mh, Builtin_Numeric_Type)
1210 elif operator == Unary_Operator.CONVERSION_TO_DECIMAL:
1211 self.n_operand.ensure_type(mh, Builtin_Numeric_Type)
1212 else:
1213 mh.ice_loc(self.location,
1214 "unexpected unary operation %s" % operator)
1216 def to_string(self):
1217 prefix_operators = {
1218 Unary_Operator.MINUS : "-",
1219 Unary_Operator.PLUS : "+",
1220 Unary_Operator.ABSOLUTE_VALUE : "abs ",
1221 Unary_Operator.LOGICAL_NOT : "not ",
1222 }
1223 function_calls = {
1224 Unary_Operator.STRING_LENGTH : "len",
1225 Unary_Operator.ARRAY_LENGTH : "len",
1226 Unary_Operator.CONVERSION_TO_INT : "Integer",
1227 Unary_Operator.CONVERSION_TO_DECIMAL : "Decimal"
1228 }
1230 if self.operator in prefix_operators:
1231 return prefix_operators[self.operator] + \
1232 self.n_operand.to_string()
1234 elif self.operator in function_calls:
1235 return "%s(%s)" % (function_calls[self.operator],
1236 self.n_operand.to_string())
1238 else:
1239 assert False
1241 def dump(self, indent=0): # pragma: no cover
1242 # lobster-exclude: Debugging feature
1243 self.write_indent(indent, f"Unary {self.operator} Expression")
1244 self.write_indent(indent + 1, f"Type: {self.typ.name}")
1245 self.n_operand.dump(indent + 1)
1247 def evaluate(self, mh, context):
1248 # lobster-trace: LRM.Null_Is_Invalid
1249 # lobster-trace: LRM.Signature_Len
1250 # lobster-trace: LRM.Signature_Type_Conversion
1251 # lobster-trace: LRM.Len_Semantics
1252 # lobster-trace: LRM.Integer_Conversion_Semantics
1253 # lobster-trace: LRM.Decimal_Conversion_Semantics
1255 assert isinstance(mh, Message_Handler)
1256 assert context is None or isinstance(context, dict)
1258 v_operand = self.n_operand.evaluate(mh, context)
1259 if v_operand.value is None: 1259 ↛ 1260line 1259 didn't jump to line 1260 because the condition on line 1259 was never true
1260 mh.error(v_operand.location,
1261 "input to unary expression %s (%s) must not be null" %
1262 (self.to_string(),
1263 mh.cross_file_reference(self.location)))
1265 if self.operator == Unary_Operator.MINUS:
1266 return Value(location = self.location,
1267 value = -v_operand.value,
1268 typ = self.typ)
1269 elif self.operator == Unary_Operator.PLUS:
1270 return Value(location = self.location,
1271 value = +v_operand.value,
1272 typ = self.typ)
1273 elif self.operator == Unary_Operator.LOGICAL_NOT:
1274 return Value(location = self.location,
1275 value = not v_operand.value,
1276 typ = self.typ)
1277 elif self.operator == Unary_Operator.ABSOLUTE_VALUE:
1278 return Value(location = self.location,
1279 value = abs(v_operand.value),
1280 typ = self.typ)
1281 elif self.operator in (Unary_Operator.STRING_LENGTH,
1282 Unary_Operator.ARRAY_LENGTH):
1283 return Value(location = self.location,
1284 value = len(v_operand.value),
1285 typ = self.typ)
1286 elif self.operator == Unary_Operator.CONVERSION_TO_INT:
1287 if isinstance(v_operand.value, Fraction): 1287 ↛ 1293line 1287 didn't jump to line 1293 because the condition on line 1287 was always true
1288 return Value(
1289 location = self.location,
1290 value = math.round_nearest_away(v_operand.value),
1291 typ = self.typ)
1292 else:
1293 return Value(location = self.location,
1294 value = v_operand.value,
1295 typ = self.typ)
1296 elif self.operator == Unary_Operator.CONVERSION_TO_DECIMAL:
1297 return Value(location = self.location,
1298 value = Fraction(v_operand.value),
1299 typ = self.typ)
1300 else:
1301 mh.ice_loc(self.location,
1302 "unexpected unary operation %s" % self.operator)
1304 def to_python_object(self):
1305 assert self.operator in (Unary_Operator.MINUS,
1306 Unary_Operator.PLUS)
1307 val = self.n_operand.to_python_object()
1308 if self.operator == Unary_Operator.MINUS:
1309 return -val
1310 else:
1311 return val
1313 def can_be_null(self):
1314 return False
1317class Binary_Expression(Expression):
1318 """Expression with two operands
1320 This captures the following operations:
1322 * Binary_Operator.LOGICAL_AND (e.g. ``a and b``)
1323 * Binary_Operator.LOGICAL_OR (e.g. ``a or b``)
1324 * Binary_Operator.LOGICAL_XOR (e.g. ``a xor b``)
1325 * Binary_Operator.LOGICAL_IMPLIES (e.g. ``a implies b``)
1326 * Binary_Operator.COMP_EQ (e.g. ``a == null``)
1327 * Binary_Operator.COMP_NEQ (e.g. ``a != null``)
1328 * Binary_Operator.COMP_LT (e.g. ``1 < 2``)
1329 * Binary_Operator.COMP_LEQ (e.g. ``1 <= 2``)
1330 * Binary_Operator.COMP_GT (e.g. ``a > b``)
1331 * Binary_Operator.COMP_GEQ (e.g. ``a >= b``)
1332 * Binary_Operator.STRING_CONTAINS (e.g. ``"foo" in "foobar"``)
1333 * Binary_Operator.STRING_STARTSWITH (e.g. ``startswith("foo", "f")``)
1334 * Binary_Operator.STRING_ENDSWITH (e.g. ``endswith("foo", "o")``)
1335 * Binary_Operator.STRING_REGEX (e.g. ``matches("foo", ".o.``)
1336 * Binary_Operator.ARRAY_CONTAINS (e.g. ``42 in arr``)
1337 * Binary_Operator.PLUS (e.g. ``42 + b`` or ``"foo" + bar``)
1338 * Binary_Operator.MINUS (e.g. ``a - 1``)
1339 * Binary_Operator.TIMES (e.g. ``2 * x``)
1340 * Binary_Operator.DIVIDE (e.g. ``x / 2``)
1341 * Binary_Operator.REMAINDER (e.g. ``x % 2``)
1342 * Binary_Operator.POWER (e.g. ``x ** 2``)
1343 * Binary_Operator.INDEX (e.g. ``foo[2]``)
1345 Note that several builtin functions are mapped to unary operators.
1347 Note also that the plus operation is supported for integers,
1348 rationals and strings.
1350 :attribute operator: the operation
1351 :type: Binary_Operator
1353 :attribute n_lhs: the first operand
1354 :type: Expression
1356 :attribute n_rhs: the second operand
1357 :type: Expression
1359 """
1360 def __init__(self, mh, location, typ, operator, n_lhs, n_rhs):
1361 # lobster-trace: LRM.Expression
1362 # lobster-trace: LRM.Relation
1363 # lobster-trace: LRM.Simple_Expression
1364 # lobster-trace: LRM.Term
1365 # lobster-trace: LRM.Factor
1366 # lobster-trace: LRM.Signature_String_End_Functions
1367 # lobster-trace: LRM.Signature_Matches
1369 super().__init__(location, typ)
1370 assert isinstance(mh, Message_Handler)
1371 assert isinstance(operator, Binary_Operator)
1372 assert isinstance(n_lhs, Expression)
1373 assert isinstance(n_rhs, Expression)
1374 self.operator = operator
1375 self.n_lhs = n_lhs
1376 self.n_rhs = n_rhs
1378 if operator in (Binary_Operator.LOGICAL_AND,
1379 Binary_Operator.LOGICAL_OR,
1380 Binary_Operator.LOGICAL_XOR,
1381 Binary_Operator.LOGICAL_IMPLIES):
1382 self.n_lhs.ensure_type(mh, Builtin_Boolean)
1383 self.n_rhs.ensure_type(mh, Builtin_Boolean)
1385 elif operator in (Binary_Operator.COMP_EQ,
1386 Binary_Operator.COMP_NEQ):
1387 if (self.n_lhs.typ is None) or (self.n_rhs.typ is None):
1388 # We can compary anything to null (including itself)
1389 pass
1390 elif self.n_lhs.typ != self.n_rhs.typ:
1391 # Otherwise we can compare anything, as long as the
1392 # types match
1393 mh.error(self.location,
1394 "type mismatch: %s and %s do not match" %
1395 (self.n_lhs.typ.name,
1396 self.n_rhs.typ.name))
1398 elif operator in (Binary_Operator.COMP_LT,
1399 Binary_Operator.COMP_LEQ,
1400 Binary_Operator.COMP_GT,
1401 Binary_Operator.COMP_GEQ):
1402 self.n_lhs.ensure_type(mh, Builtin_Numeric_Type)
1403 self.n_rhs.ensure_type(mh, self.n_lhs.typ)
1405 elif operator in (Binary_Operator.STRING_CONTAINS,
1406 Binary_Operator.STRING_STARTSWITH,
1407 Binary_Operator.STRING_ENDSWITH,
1408 Binary_Operator.STRING_REGEX):
1409 self.n_lhs.ensure_type(mh, Builtin_String)
1410 self.n_rhs.ensure_type(mh, Builtin_String)
1412 elif operator == Binary_Operator.ARRAY_CONTAINS:
1413 self.n_rhs.ensure_type(mh, Array_Type)
1414 self.n_lhs.ensure_type(mh, self.n_rhs.typ.element_type.__class__)
1416 elif operator == Binary_Operator.PLUS:
1417 if isinstance(self.n_lhs.typ, Builtin_Numeric_Type):
1418 self.n_rhs.ensure_type(mh, self.n_lhs.typ)
1419 else:
1420 self.n_lhs.ensure_type(mh, Builtin_String)
1421 self.n_rhs.ensure_type(mh, Builtin_String)
1423 elif operator in (Binary_Operator.MINUS,
1424 Binary_Operator.TIMES,
1425 Binary_Operator.DIVIDE):
1426 self.n_lhs.ensure_type(mh, Builtin_Numeric_Type)
1427 self.n_rhs.ensure_type(mh, self.n_lhs.typ)
1429 elif operator == Binary_Operator.POWER:
1430 self.n_lhs.ensure_type(mh, Builtin_Numeric_Type)
1431 self.n_rhs.ensure_type(mh, Builtin_Integer)
1433 elif operator == Binary_Operator.REMAINDER:
1434 self.n_lhs.ensure_type(mh, Builtin_Integer)
1435 self.n_rhs.ensure_type(mh, Builtin_Integer)
1437 elif operator == Binary_Operator.INDEX:
1438 self.n_lhs.ensure_type(mh, Array_Type)
1439 self.n_rhs.ensure_type(mh, Builtin_Integer)
1441 else:
1442 mh.ice_loc(self.location,
1443 "unexpected binary operation %s" % operator)
1445 def dump(self, indent=0): # pragma: no cover
1446 # lobster-exclude: Debugging feature
1447 self.write_indent(indent, f"Binary {self.operator} Expression")
1448 self.write_indent(indent + 1, f"Type: {self.typ.name}")
1449 self.n_lhs.dump(indent + 1)
1450 self.n_rhs.dump(indent + 1)
1452 def to_string(self):
1453 infix_operators = {
1454 Binary_Operator.LOGICAL_AND : "and",
1455 Binary_Operator.LOGICAL_OR : "or",
1456 Binary_Operator.LOGICAL_XOR : "xor",
1457 Binary_Operator.LOGICAL_IMPLIES : "implies",
1458 Binary_Operator.COMP_EQ : "==",
1459 Binary_Operator.COMP_NEQ : "!=",
1460 Binary_Operator.COMP_LT : "<",
1461 Binary_Operator.COMP_LEQ : "<=",
1462 Binary_Operator.COMP_GT : ">",
1463 Binary_Operator.COMP_GEQ : ">=",
1464 Binary_Operator.STRING_CONTAINS : "in",
1465 Binary_Operator.ARRAY_CONTAINS : "in",
1466 Binary_Operator.PLUS : "+",
1467 Binary_Operator.MINUS : "-",
1468 Binary_Operator.TIMES : "*",
1469 Binary_Operator.DIVIDE : "/",
1470 Binary_Operator.REMAINDER : "%",
1471 Binary_Operator.POWER : "**",
1472 }
1473 string_functions = {
1474 Binary_Operator.STRING_STARTSWITH : "startswith",
1475 Binary_Operator.STRING_ENDSWITH : "endswith",
1476 Binary_Operator.STRING_REGEX : "matches",
1477 }
1479 if self.operator in infix_operators:
1480 return "%s %s %s" % (self.n_lhs.to_string(),
1481 infix_operators[self.operator],
1482 self.n_rhs.to_string())
1484 elif self.operator in string_functions:
1485 return "%s(%s, %s)" % (string_functions[self.operator],
1486 self.n_lhs.to_string(),
1487 self.n_rhs.to_string())
1489 elif self.operator == Binary_Operator.INDEX:
1490 return "%s[%s]" % (self.n_lhs.to_string(),
1491 self.n_rhs.to_string())
1493 else:
1494 assert False
1496 def evaluate(self, mh, context):
1497 # lobster-trace: LRM.Null_Equivalence
1498 # lobster-trace: LRM.Null_Is_Invalid
1499 # lobster-trace: LRM.Signature_String_End_Functions
1500 # lobster-trace: LRM.Signature_Matches
1501 # lobster-trace: LRM.Startswith_Semantics
1502 # lobster-trace: LRM.Endswith_Semantics
1503 # lobster-trace: LRM.Matches_Semantics
1505 assert isinstance(mh, Message_Handler)
1506 assert context is None or isinstance(context, dict)
1508 v_lhs = self.n_lhs.evaluate(mh, context)
1509 if v_lhs.value is None and \
1510 self.operator not in (Binary_Operator.COMP_EQ,
1511 Binary_Operator.COMP_NEQ):
1512 mh.error(v_lhs.location,
1513 "lhs of check %s (%s) must not be null" %
1514 (self.to_string(),
1515 mh.cross_file_reference(self.location)))
1517 # Check for the short-circuit operators first
1518 if self.operator == Binary_Operator.LOGICAL_AND:
1519 assert isinstance(v_lhs.value, bool)
1520 if v_lhs.value:
1521 return self.n_rhs.evaluate(mh, context)
1522 else:
1523 return v_lhs
1525 elif self.operator == Binary_Operator.LOGICAL_OR:
1526 assert isinstance(v_lhs.value, bool)
1527 if v_lhs.value:
1528 return v_lhs
1529 else:
1530 return self.n_rhs.evaluate(mh, context)
1532 elif self.operator == Binary_Operator.LOGICAL_IMPLIES:
1533 assert isinstance(v_lhs.value, bool)
1534 if v_lhs.value:
1535 return self.n_rhs.evaluate(mh, context)
1536 else:
1537 return Value(location = self.location,
1538 value = True,
1539 typ = self.typ)
1541 # Otherwise, evaluate RHS and do the operation
1542 v_rhs = self.n_rhs.evaluate(mh, context)
1543 if v_rhs.value is None and \
1544 self.operator not in (Binary_Operator.COMP_EQ,
1545 Binary_Operator.COMP_NEQ):
1546 mh.error(v_rhs.location,
1547 "rhs of check %s (%s) must not be null" %
1548 (self.to_string(),
1549 mh.cross_file_reference(self.location)))
1551 if self.operator == Binary_Operator.LOGICAL_XOR:
1552 assert isinstance(v_lhs.value, bool)
1553 assert isinstance(v_rhs.value, bool)
1554 return Value(location = self.location,
1555 value = v_lhs.value ^ v_rhs.value,
1556 typ = self.typ)
1558 elif self.operator == Binary_Operator.COMP_EQ:
1559 return Value(location = self.location,
1560 value = v_lhs.value == v_rhs.value,
1561 typ = self.typ)
1563 elif self.operator == Binary_Operator.COMP_NEQ:
1564 return Value(location = self.location,
1565 value = v_lhs.value != v_rhs.value,
1566 typ = self.typ)
1568 elif self.operator in (Binary_Operator.COMP_LT,
1569 Binary_Operator.COMP_LEQ,
1570 Binary_Operator.COMP_GT,
1571 Binary_Operator.COMP_GEQ):
1572 return Value(
1573 location = self.location,
1574 value = {
1575 Binary_Operator.COMP_LT : lambda lhs, rhs: lhs < rhs,
1576 Binary_Operator.COMP_LEQ : lambda lhs, rhs: lhs <= rhs,
1577 Binary_Operator.COMP_GT : lambda lhs, rhs: lhs > rhs,
1578 Binary_Operator.COMP_GEQ : lambda lhs, rhs: lhs >= rhs,
1579 }[self.operator](v_lhs.value, v_rhs.value),
1580 typ = self.typ)
1582 elif self.operator == Binary_Operator.STRING_CONTAINS:
1583 assert isinstance(v_lhs.value, str)
1584 assert isinstance(v_rhs.value, str)
1586 return Value(location = self.location,
1587 value = v_lhs.value in v_rhs.value,
1588 typ = self.typ)
1590 elif self.operator == Binary_Operator.STRING_STARTSWITH:
1591 assert isinstance(v_lhs.value, str)
1592 assert isinstance(v_rhs.value, str)
1593 return Value(location = self.location,
1594 value = v_lhs.value.startswith(v_rhs.value),
1595 typ = self.typ)
1597 elif self.operator == Binary_Operator.STRING_ENDSWITH:
1598 assert isinstance(v_lhs.value, str)
1599 assert isinstance(v_rhs.value, str)
1600 return Value(location = self.location,
1601 value = v_lhs.value.endswith(v_rhs.value),
1602 typ = self.typ)
1604 elif self.operator == Binary_Operator.STRING_REGEX:
1605 assert isinstance(v_lhs.value, str)
1606 assert isinstance(v_rhs.value, str)
1607 return Value(location = self.location,
1608 value = re.match(v_rhs.value,
1609 v_lhs.value) is not None,
1610 typ = self.typ)
1612 elif self.operator == Binary_Operator.ARRAY_CONTAINS:
1613 assert isinstance(v_rhs.value, list)
1615 return Value(location = self.location,
1616 value = v_lhs in v_rhs.value,
1617 typ = self.typ)
1619 elif self.operator == Binary_Operator.PLUS:
1620 assert isinstance(v_lhs.value, (int, str, Fraction))
1621 assert isinstance(v_rhs.value, (int, str, Fraction))
1622 return Value(location = self.location,
1623 value = v_lhs.value + v_rhs.value,
1624 typ = self.typ)
1626 elif self.operator == Binary_Operator.MINUS:
1627 assert isinstance(v_lhs.value, (int, Fraction))
1628 assert isinstance(v_rhs.value, (int, Fraction))
1629 return Value(location = self.location,
1630 value = v_lhs.value - v_rhs.value,
1631 typ = self.typ)
1633 elif self.operator == Binary_Operator.TIMES:
1634 assert isinstance(v_lhs.value, (int, Fraction))
1635 assert isinstance(v_rhs.value, (int, Fraction))
1636 return Value(location = self.location,
1637 value = v_lhs.value * v_rhs.value,
1638 typ = self.typ)
1640 elif self.operator == Binary_Operator.DIVIDE:
1641 assert isinstance(v_lhs.value, (int, Fraction))
1642 assert isinstance(v_rhs.value, (int, Fraction))
1644 if v_rhs.value == 0: 1644 ↛ 1645line 1644 didn't jump to line 1645 because the condition on line 1644 was never true
1645 mh.error(v_rhs.location,
1646 "division by zero in %s (%s)" %
1647 (self.to_string(),
1648 mh.cross_file_reference(self.location)))
1650 if isinstance(v_lhs.value, int):
1651 return Value(location = self.location,
1652 value = v_lhs.value // v_rhs.value,
1653 typ = self.typ)
1654 else:
1655 return Value(location = self.location,
1656 value = v_lhs.value / v_rhs.value,
1657 typ = self.typ)
1659 elif self.operator == Binary_Operator.REMAINDER:
1660 assert isinstance(v_lhs.value, int)
1661 assert isinstance(v_rhs.value, int)
1663 if v_rhs.value == 0: 1663 ↛ 1664line 1663 didn't jump to line 1664 because the condition on line 1663 was never true
1664 mh.error(v_rhs.location,
1665 "division by zero in %s (%s)" %
1666 (self.to_string(),
1667 mh.cross_file_reference(self.location)))
1669 return Value(location = self.location,
1670 value = math.remainder(v_lhs.value, v_rhs.value),
1671 typ = self.typ)
1673 elif self.operator == Binary_Operator.POWER:
1674 assert isinstance(v_lhs.value, (int, Fraction))
1675 assert isinstance(v_rhs.value, int)
1676 return Value(location = self.location,
1677 value = v_lhs.value ** v_rhs.value,
1678 typ = self.typ)
1680 elif self.operator == Binary_Operator.INDEX:
1681 assert isinstance(v_lhs.value, list)
1682 assert isinstance(v_rhs.value, int)
1684 if v_rhs.value < 0: 1684 ↛ 1685line 1684 didn't jump to line 1685 because the condition on line 1684 was never true
1685 mh.error(v_rhs.location,
1686 "index cannot be less than zero in %s (%s)" %
1687 (self.to_string(),
1688 mh.cross_file_reference(self.location)))
1689 elif v_lhs.typ.upper_bound is not None and \ 1689 ↛ 1691line 1689 didn't jump to line 1691 because the condition on line 1689 was never true
1690 v_rhs.value > v_lhs.typ.upper_bound:
1691 mh.error(v_rhs.location,
1692 "index cannot be more than %u in %s (%s)" %
1693 (v_lhs.typ.upper_bound,
1694 self.to_string(),
1695 mh.cross_file_reference(self.location)))
1696 elif v_rhs.value > len(v_lhs.value): 1696 ↛ 1697line 1696 didn't jump to line 1697 because the condition on line 1696 was never true
1697 mh.error(v_lhs.location,
1698 "array is not big enough in %s (%s)" %
1699 (self.to_string(),
1700 mh.cross_file_reference(self.location)))
1702 return Value(location = self.location,
1703 value = v_lhs.value[v_rhs.value].value,
1704 typ = self.typ)
1706 else:
1707 mh.ice_loc(self.location,
1708 "unexpected binary operator %s" % self.operator)
1710 def can_be_null(self):
1711 return False
1714class Field_Access_Expression(Expression):
1715 """Tuple field access
1717 For example in::
1719 foo.bar
1720 ^1 ^2
1722 :attribute n_prefix: expression with tuple type (see 1)
1723 :type: Expression
1725 :attribute n_field: a tuple field to dereference (see 2)
1726 :type: Composite_Component
1728 """
1729 def __init__(self, mh, location, n_prefix, n_field):
1730 assert isinstance(mh, Message_Handler)
1731 assert isinstance(n_prefix, Expression)
1732 assert isinstance(n_field, Composite_Component)
1733 super().__init__(location, n_field.n_typ)
1734 self.n_prefix = n_prefix
1735 self.n_field = n_field
1737 self.n_prefix.ensure_type(mh, self.n_field.member_of)
1739 def dump(self, indent=0): # pragma: no cover
1740 # lobster-exclude: Debugging feature
1741 self.write_indent(indent, f"Field_Access ({self.n_field.name})")
1742 self.n_prefix.dump(indent + 1)
1744 def to_string(self):
1745 return self.n_prefix.to_string() + "." + self.n_field.name
1747 def evaluate(self, mh, context):
1748 assert isinstance(mh, Message_Handler)
1749 assert context is None or isinstance(context, dict)
1751 return self.n_prefix.evaluate(mh, context).value[self.n_field.name]
1753 def can_be_null(self):
1754 return False
1757class Range_Test(Expression):
1758 """Range membership test
1760 For example in::
1762 x in 1 .. field+1
1763 ^lhs ^lower ^^^^^^^upper
1765 Note that none of these are guaranteed to be literals or names;
1766 you can have arbitrarily complex expressions here.
1768 :attribute n_lhs: the expression to test
1769 :type: Expression
1771 :attribute n_lower: the lower bound
1772 :type: Expression
1774 :attribute n_upper: the upper bound
1775 :type: Expression
1777 """
1778 def __init__(self, mh, location, typ, n_lhs, n_lower, n_upper):
1779 # lobster-trace: LRM.Relation
1780 super().__init__(location, typ)
1781 assert isinstance(mh, Message_Handler)
1782 assert isinstance(n_lhs, Expression)
1783 assert isinstance(n_lower, Expression)
1784 assert isinstance(n_upper, Expression)
1785 self.n_lhs = n_lhs
1786 self.n_lower = n_lower
1787 self.n_upper = n_upper
1789 self.n_lhs.ensure_type(mh, Builtin_Numeric_Type)
1790 self.n_lower.ensure_type(mh, self.n_lhs.typ)
1791 self.n_upper.ensure_type(mh, self.n_lhs.typ)
1793 def to_string(self):
1794 return "%s in %s .. %s" % (self.n_lhs.to_string(),
1795 self.n_lower.to_string(),
1796 self.n_upper.to_string())
1798 def dump(self, indent=0): # pragma: no cover
1799 # lobster-exclude: Debugging feature
1800 self.write_indent(indent, "Range Test")
1801 self.write_indent(indent + 1, f"Type: {self.typ}")
1802 self.n_lhs.dump(indent + 1)
1803 self.n_lower.dump(indent + 1)
1804 self.n_upper.dump(indent + 1)
1806 def evaluate(self, mh, context):
1807 # lobster-trace: LRM.Null_Is_Invalid
1808 assert isinstance(mh, Message_Handler)
1809 assert context is None or isinstance(context, dict)
1811 v_lhs = self.n_lhs.evaluate(mh, context)
1812 if v_lhs.value is None: 1812 ↛ 1813line 1812 didn't jump to line 1813 because the condition on line 1812 was never true
1813 mh.error(v_lhs.location,
1814 "lhs of range check %s (%s) see must not be null" %
1815 (self.to_string(),
1816 mh.cross_file_reference(self.location)))
1818 v_lower = self.n_lower.evaluate(mh, context)
1819 if v_lower.value is None: 1819 ↛ 1820line 1819 didn't jump to line 1820 because the condition on line 1819 was never true
1820 mh.error(v_lower.location,
1821 "lower bound of range check %s (%s) must not be null" %
1822 (self.to_string(),
1823 mh.cross_file_reference(self.location)))
1825 v_upper = self.n_upper.evaluate(mh, context)
1826 if v_upper.value is None: 1826 ↛ 1827line 1826 didn't jump to line 1827 because the condition on line 1826 was never true
1827 mh.error(v_upper.location,
1828 "upper bound of range check %s (%s) must not be null" %
1829 (self.to_string(),
1830 mh.cross_file_reference(self.location)))
1832 return Value(location = self.location,
1833 value = v_lower.value <= v_lhs.value <= v_upper.value,
1834 typ = self.typ)
1836 def can_be_null(self):
1837 return False
1840class OneOf_Expression(Expression):
1841 """OneOf expression
1843 For example in::
1845 oneof(a, b, c)
1846 ^^^^^^^ choices
1848 :attribute choices: a list of boolean expressions to test
1849 :type: list[Expression]
1850 """
1851 def __init__(self, mh, location, typ, choices):
1852 # lobster-trace: LRM.Signature_OneOf
1853 super().__init__(location, typ)
1854 assert isinstance(typ, Builtin_Boolean)
1855 assert isinstance(mh, Message_Handler)
1856 assert isinstance(choices, list)
1857 assert all(isinstance(item, Expression)
1858 for item in choices)
1859 self.choices = choices
1861 for n_choice in choices:
1862 n_choice.ensure_type(mh, Builtin_Boolean)
1864 def to_string(self):
1865 return "oneof(%s)" % ", ".join(n_choice.to_string()
1866 for n_choice in self.choices)
1868 def dump(self, indent=0): # pragma: no cover
1869 # lobster-exclude: Debugging feature
1870 self.write_indent(indent, "OneOf Test")
1871 self.write_indent(indent + 1, f"Type: {self.typ}")
1872 for n_choice in self.choices:
1873 n_choice.dump(indent + 1)
1875 def evaluate(self, mh, context):
1876 # lobster-trace: LRM.OneOf_Semantics
1877 assert isinstance(mh, Message_Handler)
1878 assert context is None or isinstance(context, dict)
1880 v_choices = [n_choice.evaluate(mh, context).value
1881 for n_choice in self.choices]
1883 return Value(location = self.location,
1884 value = v_choices.count(True) == 1,
1885 typ = self.typ)
1887 def can_be_null(self):
1888 return False
1891class Action(Node):
1892 """An if or elseif part inside a conditional expression
1894 Each :class:`Conditional_Expression` is made up of a sequence of
1895 Actions. For example here is a single expression with two
1896 Actions::
1898 (if x == 0 then "zero" elsif x == 1 then "one" else "lots")
1899 ^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^
1901 Note that the else part is not an action, it is an attribute of
1902 the :class:`Conditional_Expression` itself.
1904 :attribute kind: Either if or elseif
1905 :type: str
1907 :attribute n_cond: The boolean condition expression
1908 :type: Expression
1910 :attribute n_expr: The value if the condition evaluates to true
1911 :type: Expression
1913 """
1914 def __init__(self, mh, t_kind, n_condition, n_expression):
1915 # lobster-trace: LRM.Conditional_Expression
1916 assert isinstance(mh, Message_Handler)
1917 assert isinstance(t_kind, Token)
1918 assert t_kind.kind == "KEYWORD"
1919 assert t_kind.value in ("if", "elsif")
1920 assert isinstance(n_condition, Expression)
1921 assert isinstance(n_expression, Expression)
1922 super().__init__(t_kind.location)
1923 self.kind = t_kind.value
1924 self.n_cond = n_condition
1925 self.n_expr = n_expression
1926 # lobster-trace: LRM.Conditional_Expression_Types
1927 self.n_cond.ensure_type(mh, Builtin_Boolean)
1929 def dump(self, indent=0): # pragma: no cover
1930 # lobster-exclude: Debugging feature
1931 self.write_indent(indent, f"{self.kind.capitalize()} Action")
1932 self.write_indent(indent + 1, "Condition")
1933 self.n_cond.dump(indent + 2)
1934 self.write_indent(indent + 1, "Value")
1935 self.n_expr.dump(indent + 2)
1937 def to_string(self):
1938 return "%s %s then %s" % (self.kind,
1939 self.n_cond.to_string(),
1940 self.n_expr.to_string())
1943class Conditional_Expression(Expression):
1944 """A conditional expression
1946 Each :class:`Conditional_Expression` is made up of a sequence of
1947 one or more :class:`Action`. For example here is a single
1948 expression with two Actions::
1950 (if x == 0 then "zero" elsif x == 1 then "one" else "lots")
1951 ^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^
1953 The else expression is part of the conditional expression itself.
1955 A conditional expression will have at least one action (the if
1956 action), and all other actions will be elsif actions. The else
1957 expression is not optional and will always be present. The types
1958 of all actions and the else expression will match.
1960 :attribute actions: a list of Actions
1961 :type: list[Action]
1963 :attribute else_expr: the else expression
1964 :type: Expression
1966 """
1967 def __init__(self, location, if_action):
1968 # lobster-trace: LRM.Conditional_Expression
1969 assert isinstance(if_action, Action)
1970 assert if_action.kind == "if"
1971 super().__init__(location, if_action.n_expr.typ)
1972 self.actions = [if_action]
1973 self.else_expr = None
1975 def add_elsif(self, mh, n_action):
1976 # lobster-trace: LRM.Conditional_Expression
1977 # lobster-trace; LRM.Conditional_Expression_Types
1978 assert isinstance(mh, Message_Handler)
1979 assert isinstance(n_action, Action)
1980 assert n_action.kind == "elsif"
1982 n_action.n_expr.ensure_type(mh, self.typ)
1983 self.actions.append(n_action)
1985 def set_else_part(self, mh, n_expr):
1986 # lobster-trace: LRM.Conditional_Expression
1987 # lobster-trace; LRM.Conditional_Expression_Types
1988 assert isinstance(mh, Message_Handler)
1989 assert isinstance(n_expr, Expression)
1991 n_expr.ensure_type(mh, self.typ)
1992 self.else_expr = n_expr
1994 def dump(self, indent=0): # pragma: no cover
1995 # lobster-exclude: Debugging feature
1996 self.write_indent(indent, "Conditional expression")
1997 for action in self.actions:
1998 action.dump(indent + 1)
1999 self.write_indent(indent + 1, "Else")
2000 self.else_expr.dump(indent + 2)
2002 def to_string(self):
2003 rv = "(" + " ".join(action.to_string()
2004 for action in self.actions)
2005 rv += " else %s" % self.else_expr.to_string()
2006 rv += ")"
2007 return rv
2009 def evaluate(self, mh, context):
2010 # lobster-trace: LRM.Conditional_Expression_Else
2011 # lobster-trace: LRM.Conditional_Expression_Evaluation
2012 # lobster-trace: LRM.Null_Is_Invalid
2013 assert isinstance(mh, Message_Handler)
2014 assert context is None or isinstance(context, dict)
2016 for action in self.actions:
2017 v_cond = action.n_cond.evaluate(mh, context)
2018 if v_cond.value is None: 2018 ↛ 2019line 2018 didn't jump to line 2019 because the condition on line 2018 was never true
2019 mh.error(v_cond.location,
2020 "condition of %s (%s) must not be null" %
2021 (action.to_string(),
2022 mh.cross_file_reference(self.location)))
2023 if v_cond.value:
2024 return action.n_expr.evaluate(mh, context)
2026 return self.else_expr.evaluate(mh, context)
2028 def can_be_null(self):
2029 if self.else_expr and self.else_expr.can_be_null():
2030 return True
2032 return any(action.n_expr.can_be_null()
2033 for action in self.actions)
2036class Quantified_Expression(Expression):
2037 """A quantified expression
2039 For example::
2041 (forall x in array_component => x > 0)
2042 ^4 ^1 ^2 ^^^^^3
2044 A quantified expression introduces and binds a
2045 :class:`Quantified_Variable` (see 1) from a specified source (see
2046 2). When the body (see 3) is evaluated, the name of 1 is bound to
2047 each component of the source in turn.
2049 :attribute n_var: The quantified variable (see 1)
2050 :type: Quantified_Variable
2052 :attribute n_source: The array to iterate over (see 2)
2053 :type: Name_Reference
2055 :attribute n_expr: The body of the quantifier (see 3)
2056 :type: Expression
2058 :attribute universal: True means forall, false means exists (see 4)
2059 :type: Boolean
2061 """
2062 def __init__(self, mh, location,
2063 typ,
2064 universal,
2065 n_variable,
2066 n_source,
2067 n_expr):
2068 # lobster-trace: LRM.Quantified_Expression
2069 # lobster-trace: LRM.Quantification_Type
2070 super().__init__(location, typ)
2071 assert isinstance(typ, Builtin_Boolean)
2072 assert isinstance(universal, bool)
2073 assert isinstance(n_variable, Quantified_Variable)
2074 assert isinstance(n_expr, Expression)
2075 assert isinstance(n_source, Name_Reference)
2076 self.universal = universal
2077 self.n_var = n_variable
2078 self.n_expr = n_expr
2079 self.n_source = n_source
2080 self.n_expr.ensure_type(mh, Builtin_Boolean)
2082 def dump(self, indent=0): # pragma: no cover
2083 # lobster-exclude: Debugging feature
2084 if self.universal:
2085 self.write_indent(indent, "Universal quantified expression")
2086 else:
2087 self.write_indent(indent, "Existential quantified expression")
2088 self.n_var.dump(indent + 1)
2089 self.n_expr.dump(indent + 1)
2091 def to_string(self):
2092 return "(%s %s in %s => %s)" % ("forall"
2093 if self.universal
2094 else "exists",
2095 self.n_var.name,
2096 self.n_source.to_string(),
2097 self.n_expr.to_string())
2099 def evaluate(self, mh, context):
2100 # lobster-trace: LRM.Null_Is_Invalid
2101 # lobster-trace: LRM.Universal_Quantification_Semantics
2102 # lobster-trace: LRM.Existential_Quantification_Semantics
2103 assert isinstance(mh, Message_Handler)
2104 assert context is None or isinstance(context, dict)
2106 if context is None: 2106 ↛ 2107line 2106 didn't jump to line 2107 because the condition on line 2106 was never true
2107 new_ctx = {}
2108 else:
2109 new_ctx = copy(context)
2111 # This is going to be a bit tricky. We essentially eliminate
2112 # the quantifier and substitute; for the sake of making better
2113 # error messages.
2114 assert isinstance(self.n_source.entity, Composite_Component)
2115 array_values = context[self.n_source.entity.name]
2116 if isinstance(array_values, Implicit_Null):
2117 mh.error(array_values.location,
2118 "%s in quantified expression %s (%s) "
2119 "must not be null" %
2120 (self.n_source.to_string(),
2121 self.to_string(),
2122 mh.cross_file_reference(self.location)))
2123 else:
2124 assert isinstance(array_values, Array_Aggregate)
2126 rv = self.universal
2127 loc = self.location
2128 for binding in array_values.value:
2129 new_ctx[self.n_var.name] = binding
2130 result = self.n_expr.evaluate(mh, new_ctx)
2131 assert isinstance(result.value, bool)
2132 if self.universal and not result.value:
2133 rv = False
2134 loc = binding.location
2135 break
2136 elif not self.universal and result.value:
2137 rv = True
2138 loc = binding.location
2139 break
2141 return Value(location = loc,
2142 value = rv,
2143 typ = self.typ)
2145 def can_be_null(self):
2146 return False
2149##############################################################################
2150# AST Nodes (Entities)
2151##############################################################################
2153class Entity(Node, metaclass=ABCMeta):
2154 """Base class for all entities.
2156 An entity is a concrete object (with a name) for which we need to
2157 allocate memory. Examples of entities are types and record
2158 objects.
2160 :attribute name: unqualified name of the entity
2161 :type: str
2163 """
2164 def __init__(self, name, location):
2165 # lobster-trace: LRM.Described_Name_Equality
2166 super().__init__(location)
2167 assert isinstance(name, str)
2168 self.name = name
2171class Typed_Entity(Entity, metaclass=ABCMeta):
2172 """Base class for entities with a type.
2174 A typed entity is a concrete object (with a name and TRLC type)
2175 for which we need to allocate memory. Examples of typed entities
2176 are record objects and components.
2178 :attribute n_typ: type of the entity
2179 :type: Type
2181 """
2182 def __init__(self, name, location, n_typ):
2183 # lobster-exclude: Constructor only declares variables
2184 super().__init__(name, location)
2185 assert isinstance(n_typ, Type)
2186 self.n_typ = n_typ
2189class Quantified_Variable(Typed_Entity):
2190 """Variable used in quantified expression.
2192 A quantified expression declares and binds a variable, for which
2193 we need a named entity. For example in::
2195 (forall x in array => x > 1)
2196 ^
2198 We represent this first x as a :class:`Quantified_Variable`, the
2199 second x will be an ordinary :class:`Name_Reference`.
2201 :attribute typ: type of the variable (i.e. element type of the array)
2202 :type: Type
2204 """
2205 def dump(self, indent=0): # pragma: no cover
2206 # lobster-exclude: Debugging feature
2207 self.write_indent(indent, f"Quantified Variable {self.name}")
2208 self.n_typ.dump(indent + 1)
2211class Type(Entity, metaclass=ABCMeta):
2212 """Abstract base class for all types.
2214 """
2215 def perform_type_checks(self, mh, value):
2216 assert isinstance(mh, Message_Handler)
2217 assert isinstance(value, Expression)
2218 return True
2220 def get_example_value(self):
2221 # lobster-exclude: utility method
2222 assert False
2225class Concrete_Type(Type, metaclass=ABCMeta):
2226 # lobster-trace: LRM.Type_Declarations
2227 """Abstract base class for all non-anonymous types.
2229 :attribute n_package: package where this type was declared
2230 :type: Package
2231 """
2232 def __init__(self, name, location, n_package):
2233 super().__init__(name, location)
2234 assert isinstance(n_package, Package)
2235 self.n_package = n_package
2237 def fully_qualified_name(self):
2238 """Return the FQN for this type (i.e. PACKAGE.NAME)
2240 :returns: the type's full name
2241 :rtype: str
2242 """
2243 return self.n_package.name + "." + self.name
2245 def __hash__(self):
2246 return hash((self.n_package.name, self.name))
2248 def __repr__(self):
2249 return "%s<%s>" % (self.__class__.__name__,
2250 self.fully_qualified_name())
2253class Builtin_Type(Type, metaclass=ABCMeta):
2254 # lobster-trace: LRM.Builtin_Types
2255 """Abstract base class for all builtin types.
2257 """
2258 LOCATION = Location(file_name = "<builtin>")
2260 def __init__(self, name):
2261 super().__init__(name, Builtin_Type.LOCATION)
2263 def dump(self, indent=0): # pragma: no cover
2264 self.write_indent(indent, self.__class__.__name__)
2267class Builtin_Numeric_Type(Builtin_Type, metaclass=ABCMeta):
2268 # lobster-trace: LRM.Builtin_Types
2269 """Abstract base class for all builtin numeric types.
2271 """
2272 def dump(self, indent=0): # pragma: no cover
2273 self.write_indent(indent, self.__class__.__name__)
2276class Builtin_Function(Entity):
2277 # lobster-trace: LRM.Builtin_Functions
2278 """Builtin functions.
2280 These are auto-generated by the :class:`~trlc.trlc.Source_Manager`.
2282 :attribute arity: number of parameters
2283 :type: int
2285 :attribute arity_at_least: when true, arity indicates a lower bound
2286 :type: bool
2288 """
2289 LOCATION = Location(file_name = "<builtin>")
2291 def __init__(self, name, arity, arity_at_least=False):
2292 super().__init__(name, Builtin_Function.LOCATION)
2293 assert isinstance(arity, int)
2294 assert isinstance(arity_at_least, bool)
2295 assert arity >= 0
2296 self.arity = arity
2297 self.arity_at_least = arity_at_least
2299 def dump(self, indent=0): # pragma: no cover
2300 self.write_indent(indent, self.__class__.__name__ + " " + self.name)
2303class Array_Type(Type):
2304 """Anonymous array type.
2306 These are declared implicitly for each record component that has
2307 an array specifier::
2309 foo Integer [5 .. *]
2310 ^
2312 :attribute lower_bound: minimum number of elements
2313 :type: int
2315 :attribute loc_lower: text location of the lower bound indicator
2316 :type: Location
2318 :attribute upper_bound: maximum number of elements (or None)
2319 :type: int
2321 :attribute loc_upper: text location of the upper bound indicator
2322 :type: Location
2324 :attribute element_type: type of the array elements
2325 :type: Type
2327 """
2328 def __init__(self,
2329 location,
2330 element_type,
2331 loc_lower,
2332 lower_bound,
2333 loc_upper,
2334 upper_bound):
2335 # lobster-exclude: Constructor only declares variables
2336 assert isinstance(element_type, Type) or element_type is None
2337 assert isinstance(lower_bound, int)
2338 assert lower_bound >= 0
2339 assert upper_bound is None or isinstance(upper_bound, int)
2340 assert upper_bound is None or upper_bound >= 0
2341 assert isinstance(loc_lower, Location)
2342 assert isinstance(loc_upper, Location)
2344 if element_type is None: 2344 ↛ 2345line 2344 didn't jump to line 2345 because the condition on line 2344 was never true
2345 name = "universal array"
2346 elif upper_bound is None:
2347 if lower_bound == 0:
2348 name = "array of %s" % element_type.name
2349 else:
2350 name = "array of at least %u %s" % (lower_bound,
2351 element_type.name)
2352 elif lower_bound == upper_bound:
2353 name = "array of %u %s" % (lower_bound,
2354 element_type.name)
2355 else:
2356 name = "array of %u to %u %s" % (lower_bound,
2357 upper_bound,
2358 element_type.name)
2359 super().__init__(name, location)
2360 self.lower_bound = lower_bound
2361 self.loc_lower = loc_lower
2362 self.upper_bound = upper_bound
2363 self.loc_upper = loc_upper
2364 self.element_type = element_type
2366 def dump(self, indent=0): # pragma: no cover
2367 # lobster-exclude: Debugging feature
2368 self.write_indent(indent, "Array_Type")
2369 self.write_indent(indent + 1, f"Lower bound: {self.lower_bound}")
2370 if self.upper_bound is None:
2371 self.write_indent(indent + 1, "Upper bound: *")
2372 else:
2373 self.write_indent(indent + 1, f"Upper bound: {self.upper_bound}")
2374 self.write_indent(indent + 1, f"Element type: {self.element_type.name}")
2376 def perform_type_checks(self, mh, value):
2377 assert isinstance(mh, Message_Handler)
2378 if isinstance(value, Array_Aggregate):
2379 return all(self.element_type.perform_type_checks(mh, v)
2380 for v in value.value)
2381 else:
2382 assert isinstance(value, Implicit_Null)
2383 return True
2385 def get_example_value(self):
2386 # lobster-exclude: utility method
2387 return "[%s]" % self.element_type.get_example_value()
2390class Builtin_Integer(Builtin_Numeric_Type):
2391 # lobster-trace: LRM.Builtin_Types
2392 # lobster-trace: LRM.Integer_Values
2393 """Builtin integer type."""
2394 def __init__(self):
2395 super().__init__("Integer")
2397 def get_example_value(self):
2398 # lobster-exclude: utility method
2399 return "100"
2402class Builtin_Decimal(Builtin_Numeric_Type):
2403 # lobster-trace: LRM.Builtin_Types
2404 # lobster-trace: LRM.Decimal_Values
2405 """Builtin decimal type."""
2406 def __init__(self):
2407 super().__init__("Decimal")
2409 def get_example_value(self):
2410 # lobster-exclude: utility method
2411 return "3.14"
2414class Builtin_Boolean(Builtin_Type):
2415 # lobster-trace: LRM.Builtin_Types
2416 # lobster-trace: LRM.Boolean_Values
2417 """Builtin boolean type."""
2418 def __init__(self):
2419 super().__init__("Boolean")
2421 def get_example_value(self):
2422 # lobster-exclude: utility method
2423 return "true"
2426class Builtin_String(Builtin_Type):
2427 # lobster-trace: LRM.Builtin_Types
2428 # lobster-trace: LRM.String_Values
2429 """Builtin string type."""
2430 def __init__(self):
2431 super().__init__("String")
2433 def get_example_value(self):
2434 # lobster-exclude: utility method
2435 return "\"potato\""
2438class Builtin_Markup_String(Builtin_String):
2439 # lobster-trace: LRM.Builtin_Types
2440 # lobster-trace: LRM.Markup_String_Values
2441 """Builtin string type that allows checked references to TRLC
2442 objects.
2443 """
2444 def __init__(self):
2445 super().__init__()
2446 self.name = "Markup_String"
2448 def get_example_value(self):
2449 # lobster-exclude: utility method
2450 return "\"also see [[potato]]\""
2453class Package(Entity):
2454 """Packages.
2456 A package is declared when it is first encountered (in either a
2457 rsl or trlc file). A package contains all symbols declared in it,
2458 both types and record objects. A package is not associated with
2459 just a single file, it can be spread over multiple files.
2461 :attribute declared_late: indicates if this package is declared in a \
2462 trlc file
2463 :type: bool
2465 :attribute symbols: symbol table of the package
2466 :type: Symbol_Table
2468 """
2469 def __init__(self, name, location, builtin_stab, declared_late):
2470 # lobster-exclude: Constructor only declares variables
2471 super().__init__(name, location)
2472 assert isinstance(builtin_stab, Symbol_Table)
2473 assert isinstance(declared_late, bool)
2474 self.symbols = Symbol_Table()
2475 self.symbols.make_visible(builtin_stab)
2476 self.declared_late = declared_late
2478 def dump(self, indent=0): # pragma: no cover
2479 # lobster-exclude: Debugging feature
2480 self.write_indent(indent, f"Package {self.name}")
2481 self.write_indent(indent + 1, f"Declared_Late: {self.declared_late}")
2482 self.symbols.dump(indent + 1, omit_heading=True)
2484 def __repr__(self):
2485 return "%s<%s>" % (self.__class__.__name__,
2486 self.name)
2489class Composite_Type(Concrete_Type, metaclass=ABCMeta):
2490 """Abstract base for record and tuple types, as they share some
2491 functionality.
2493 :attribute components: type components (including inherited if applicable)
2494 :type: Symbol_Table[Composite_Component]
2496 :attribute description: user-supplied description of the type or None
2497 :type: str
2499 :attribute checks: used-defined checks for this type (excluding \
2500 inherited checks)
2501 :type: list[Check]
2503 """
2504 def __init__(self,
2505 name,
2506 description,
2507 location,
2508 package,
2509 inherited_symbols=None):
2510 # lobster-trace: LRM.Described_Name_Description
2511 super().__init__(name, location, package)
2512 assert isinstance(description, str) or description is None
2513 assert isinstance(inherited_symbols, Symbol_Table) or \
2514 inherited_symbols is None
2516 self.components = Symbol_Table(inherited_symbols)
2517 self.description = description
2518 self.checks = []
2520 def add_check(self, n_check):
2521 # lobster-trace: LRM.Check_Evaluation_Order
2522 assert isinstance(n_check, Check)
2523 self.checks.append(n_check)
2525 def iter_checks(self):
2526 # lobster-trace: LRM.Check_Evaluation_Order
2527 yield from self.checks
2529 def all_components(self):
2530 # lobster-exclude: Convenience function
2531 """Convenience function to get a list of all components.
2533 :rtype: list[Composite_Component]
2534 """
2535 return list(self.components.table.values())
2538class Composite_Component(Typed_Entity):
2539 """Component in a record or tuple.
2541 When declaring a composite type, for each component an entity is
2542 declared::
2544 type|tuple T {
2545 foo "blah" optional Boolean
2546 ^1 ^2 ^3 ^4
2548 :attribute description: optional text (see 2) for this component, or None
2549 :type: str
2551 :attribute member_of: a link back to the containing record or tuple; \
2552 for inherited fields this refers back to the original base record type
2553 :type: Composite_Type
2555 :attribute optional: indicates if the component can be null or not (see 3)
2556 :type: bool
2558 """
2560 def __init__(self,
2561 name,
2562 description,
2563 location,
2564 member_of,
2565 n_typ,
2566 optional):
2567 # lobster-trace: LRM.Described_Name_Description
2568 super().__init__(name, location, n_typ)
2569 assert isinstance(description, str) or description is None
2570 assert isinstance(member_of, Composite_Type)
2571 assert isinstance(optional, bool)
2572 self.description = description
2573 self.member_of = member_of
2574 self.optional = optional
2576 def dump(self, indent=0): # pragma: no cover
2577 # lobster-exclude: Debugging feature
2578 self.write_indent(indent, f"Composite_Component {self.name}")
2579 if self.description:
2580 self.write_indent(indent + 1, f"Description: {self.description}")
2581 self.write_indent(indent + 1, f"Optional: {self.optional}")
2582 self.write_indent(indent + 1, f"Type: {self.n_typ.name}")
2584 def __repr__(self):
2585 return "%s<%s>" % (self.__class__.__name__,
2586 self.member_of.fully_qualified_name() + "." +
2587 self.name)
2590class Record_Type(Composite_Type):
2591 """A user-defined record type.
2593 In this example::
2595 type T "optional description of T" extends Root_T {
2596 ^1 ^2 ^3
2598 Note that (1) is part of the :class:`Entity` base, and (2) is part
2599 of the :class:`Composite_Type` base.
2601 :attribute parent: root type or None, indicated by (3) above
2602 :type: Record_Type
2604 :attribute frozen: mapping of frozen components
2605 :type: dict[str, Expression]
2607 :attribute is_final: type is final (i.e. no new components may be declared)
2608 :type: bool
2610 :attribute is_abstract: type is abstract
2611 :type: bool
2613 """
2614 def __init__(self,
2615 name,
2616 description,
2617 location,
2618 package,
2619 n_parent,
2620 is_abstract):
2621 # lobster-exclude: Constructor only declares variables
2622 assert isinstance(n_parent, Record_Type) or n_parent is None
2623 assert isinstance(is_abstract, bool)
2624 super().__init__(name,
2625 description,
2626 location,
2627 package,
2628 n_parent.components if n_parent else None)
2629 self.parent = n_parent
2630 self.frozen = {}
2631 self.is_final = (n_parent.is_final if n_parent else False)
2632 self.is_abstract = is_abstract
2634 def iter_checks(self):
2635 # lobster-trace: LRM.Check_Evaluation_Order
2636 # lobster-trace: LRM.Check_Evaluation_Order_For_Extensions
2637 if self.parent:
2638 yield from self.parent.iter_checks()
2639 yield from self.checks
2641 def dump(self, indent=0): # pragma: no cover
2642 # lobster-exclude: Debugging feature
2643 self.write_indent(indent, f"Record_Type {self.name}")
2644 if self.description:
2645 self.write_indent(indent + 1, f"Description: {self.description}")
2646 if self.parent:
2647 self.write_indent(indent + 1, f"Parent: {self.parent.name}")
2648 self.components.dump(indent + 1, omit_heading=True)
2649 if self.checks:
2650 self.write_indent(indent + 1, "Checks")
2651 for n_check in self.checks:
2652 n_check.dump(indent + 2)
2653 else:
2654 self.write_indent(indent + 1, "Checks: None")
2656 def all_components(self):
2657 """Convenience function to get a list of all components.
2659 :rtype: list[Composite_Component]
2660 """
2661 if self.parent:
2662 return self.parent.all_components() + \
2663 list(self.components.table.values())
2664 else:
2665 return list(self.components.table.values())
2667 def is_subclass_of(self, record_type):
2668 """ Checks if this record type is or inherits from the given type
2670 :param record_type: check if are or extend this type
2671 :type record_type: Record_Type
2673 :returns: true if we are or extend the given type
2674 :rtype: Boolean
2675 """
2676 assert isinstance(record_type, Record_Type)
2678 ptr = self
2679 while ptr:
2680 if ptr is record_type:
2681 return True
2682 else:
2683 ptr = ptr.parent
2684 return False
2686 def is_frozen(self, n_component):
2687 """Test if the given component is frozen.
2689 :param n_component: a composite component of this record type \
2690 (or any of its parents)
2691 :type n_component: Composite_Component
2693 :rtype: bool
2694 """
2695 assert isinstance(n_component, Composite_Component)
2696 if n_component.name in self.frozen:
2697 return True
2698 elif self.parent:
2699 return self.parent.is_frozen(n_component)
2700 else:
2701 return False
2703 def get_freezing_expression(self, n_component):
2704 """Retrieve the frozen value for a frozen component
2706 It is an internal compiler error to call this method with a
2707 component that his not frozen.
2709 :param n_component: a frozen component of this record type \
2710 (or any of its parents)
2711 :type n_component: Composite_Component
2713 :rtype: Expression
2715 """
2716 assert isinstance(n_component, Composite_Component)
2717 if n_component.name in self.frozen: 2717 ↛ 2719line 2717 didn't jump to line 2719 because the condition on line 2717 was always true
2718 return self.frozen[n_component.name]
2719 elif self.parent:
2720 return self.parent.get_freezing_expression(n_component)
2721 else:
2722 assert False
2724 def get_example_value(self):
2725 # lobster-exclude: utility method
2726 return "%s_instance" % self.name
2729class Tuple_Type(Composite_Type):
2730 """A user-defined tuple type.
2732 In this example::
2734 tuple T "optional description of T" {
2735 ^1 ^2
2737 Note that (1) is part of the :class:`Entity` base, and (2) is part
2738 of the :class:`Composite_Type` base.
2740 :attribute separators: list of syntactic separators.
2741 :type: list[Separator]
2743 Note the list of separators will either be empty, or there will be
2744 precisely one less separator than components.
2746 """
2747 def __init__(self, name, description, location, package):
2748 # lobster-trace: LRM.Tuple_Declaration
2749 super().__init__(name,
2750 description,
2751 location,
2752 package)
2753 self.separators = []
2755 def add_separator(self, n_separator):
2756 # lobster-exclude: utility method
2757 assert isinstance(n_separator, Separator)
2758 assert len(self.separators) + 1 == len(self.components.table)
2759 self.separators.append(n_separator)
2761 def iter_separators(self):
2762 """Iterate over all separators"""
2763 # lobster-exclude: utility method
2764 yield from self.separators
2766 def iter_sequence(self):
2767 """Iterate over all components and separators in syntactic order"""
2768 # lobster-exclude: utility method
2769 if self.separators:
2770 for i, n_component in enumerate(self.components.table.values()):
2771 yield n_component
2772 if i < len(self.separators):
2773 yield self.separators[i]
2774 else:
2775 yield from self.components.table.values()
2777 def has_separators(self):
2778 """Returns true if a tuple type requires separators"""
2779 # lobster-exclude: utility method
2780 return bool(self.separators)
2782 def dump(self, indent=0): # pragma: no cover
2783 # lobster-exclude: Debugging feature
2784 self.write_indent(indent, f"Tuple_Type {self.name}")
2785 if self.description:
2786 self.write_indent(indent + 1, f"Description: {self.description}")
2787 self.write_indent(indent + 1, "Fields")
2788 for n_item in self.iter_sequence():
2789 n_item.dump(indent + 2)
2790 if self.checks:
2791 self.write_indent(indent + 1, "Checks")
2792 for n_check in self.checks:
2793 n_check.dump(indent + 2)
2794 else:
2795 self.write_indent(indent + 1, "Checks: None")
2797 def perform_type_checks(self, mh, value):
2798 # lobster-trace: LRM.Check_Evaluation_Order
2799 assert isinstance(mh, Message_Handler)
2800 if isinstance(value, Tuple_Aggregate): 2800 ↛ 2807line 2800 didn't jump to line 2807 because the condition on line 2800 was always true
2801 ok = True
2802 for check in self.iter_checks():
2803 if not check.perform(mh, value): 2803 ↛ 2804line 2803 didn't jump to line 2804 because the condition on line 2803 was never true
2804 ok = False
2805 return ok
2806 else:
2807 assert isinstance(value, Implicit_Null)
2808 return True
2810 def get_example_value(self):
2811 # lobster-exclude: utility method
2812 parts = []
2813 for n_item in self.iter_sequence():
2814 if isinstance(n_item, Composite_Component):
2815 parts.append(n_item.n_typ.get_example_value())
2816 else:
2817 parts.append(n_item.to_string())
2818 if self.has_separators():
2819 return " ".join(parts)
2820 else:
2821 return "(%s)" % ", ".join(parts)
2824class Separator(Node):
2825 # lobster-trace: LRM.Tuple_Declaration
2826 """User-defined syntactic separator
2828 For example::
2830 separator x
2831 ^1
2833 :attribute token: token used to separate fields of the tuple
2834 :type: Token
2835 """
2836 def __init__(self, token):
2837 super().__init__(token.location)
2838 assert isinstance(token, Token) and token.kind in ("IDENTIFIER",
2839 "AT",
2840 "COLON",
2841 "SEMICOLON")
2842 self.token = token
2844 def to_string(self):
2845 return {
2846 "AT" : "@",
2847 "COLON" : ":",
2848 "SEMICOLON" : ";"
2849 }.get(self.token.kind, self.token.value)
2851 def dump(self, indent=0): # pragma: no cover
2852 self.write_indent(indent, f"Separator {self.token.value}")
2855class Enumeration_Type(Concrete_Type):
2856 """User-defined enumeration types.
2858 For example::
2860 enum T "potato" {
2861 ^1 ^2
2863 :attribute description: user supplied optional description, or None
2864 :type: str
2866 :attribute literals: the literals in this enumeration
2867 :type: Symbol_Table[Enumeration_Literal_Spec]
2869 """
2870 def __init__(self, name, description, location, package):
2871 # lobster-trace: LRM.Described_Name_Description
2872 super().__init__(name, location, package)
2873 assert isinstance(description, str) or description is None
2874 self.literals = Symbol_Table()
2875 self.description = description
2877 def dump(self, indent=0): # pragma: no cover
2878 # lobster-exclude: Debugging feature
2879 self.write_indent(indent, f"Enumeration_Type {self.name}")
2880 if self.description:
2881 self.write_indent(indent + 1, f"Description: {self.description}")
2882 self.literals.dump(indent + 1, omit_heading=True)
2884 def get_example_value(self):
2885 # lobster-exclude: utility method
2886 options = list(self.literals.values())
2887 if options:
2888 choice = len(options) // 2
2889 return self.name + "." + choice.name
2890 else:
2891 return "ERROR"
2894class Enumeration_Literal_Spec(Typed_Entity):
2895 """Declared literal in an enumeration declaration.
2897 Note that for literals mentioned later in record object
2898 declarations, we use :class:`Enumeration_Literal`. Literal specs
2899 are used here::
2901 enum ASIL {
2902 QM "not safety related"
2903 ^1 ^2
2905 :attribute description: the optional user-supplied description, or None
2906 :type: str
2908 """
2909 def __init__(self, name, description, location, enum):
2910 # lobster-trace: LRM.Described_Name_Description
2911 super().__init__(name, location, enum)
2912 assert isinstance(description, str) or description is None
2913 assert isinstance(enum, Enumeration_Type)
2914 self.description = description
2916 def dump(self, indent=0): # pragma: no cover
2917 # lobster-exclude: Debugging feature
2918 self.write_indent(indent, f"Enumeration_Literal_Spec {self.name}")
2919 if self.description:
2920 self.write_indent(indent + 1, f"Description: {self.description}")
2923class Record_Object(Typed_Entity):
2924 """A declared instance of a record type.
2926 This is going to be the bulk of all entities created by TRLC::
2928 section "Potato" {
2929 ^5
2930 Requirement PotatoReq {
2931 ^1 ^2
2932 component1 = 42
2933 ^3 ^4
2935 Note that the name (see 2) and type (see 1) of the object is
2936 provided by the name attribute of the :class:`Typed_Entity` base
2937 class.
2939 :attribute field: the specific values for all components (see 3 and 4)
2940 :type: dict[str, Expression]
2942 :attribute section: None or the section this record is contained in (see 5)
2943 :type: Section
2945 :attribute n_package: The package in which this record is declared in
2946 :type: Section
2948 The actual type of expressions in the field attribute are limited
2949 to:
2951 * :class:`Literal`
2952 * :class:`Unary_Expression`
2953 * :class:`Array_Aggregate`
2954 * :class:`Tuple_Aggregate`
2955 * :class:`Record_Reference`
2956 * :class:`Implicit_Null`
2958 """
2959 def __init__(self, name, location, n_typ, section, n_package):
2960 # lobster-trace: LRM.Section_Declaration
2961 # lobster-trace: LRM.Unspecified_Optional_Components
2962 # lobster-trace: LRM.Record_Object_Declaration
2964 assert isinstance(n_typ, Record_Type)
2965 assert isinstance(section, list) or section is None
2966 assert isinstance(n_package, Package)
2967 super().__init__(name, location, n_typ)
2968 self.field = {
2969 comp.name: Implicit_Null(self, comp)
2970 for comp in self.n_typ.all_components()
2971 }
2972 self.section = section
2973 self.n_package = n_package
2975 def fully_qualified_name(self):
2976 """Return the FQN for this type (i.e. PACKAGE.NAME)
2978 :returns: the object's full name
2979 :rtype: str
2980 """
2981 return self.n_package.name + "." + self.name
2983 def to_python_dict(self):
2984 """Return an evaluated and simplified object for Python.
2986 For example it might provide::
2988 {"foo" : [1, 2, 3],
2989 "bar" : None,
2990 "baz" : "value"}
2992 This is a function especially designed for the Python API. The
2993 name of the object itself is not in this returned dictionary.
2995 """
2996 return {name: value.to_python_object()
2997 for name, value in self.field.items()}
2999 def is_component_implicit_null(self, component) -> bool:
3000 return not isinstance(self.field[component.name], Implicit_Null)
3002 def assign(self, component, value):
3003 assert isinstance(component, Composite_Component)
3004 assert isinstance(value, (Literal,
3005 Array_Aggregate,
3006 Tuple_Aggregate,
3007 Record_Reference,
3008 Implicit_Null,
3009 Unary_Expression)), \
3010 "value is %s" % value.__class__.__name__
3011 if self.is_component_implicit_null(component): 3011 ↛ 3012line 3011 didn't jump to line 3012 because the condition on line 3011 was never true
3012 raise KeyError(f"Component {component.name} already \
3013 assigned to {self.n_typ.name} {self.name}!")
3014 self.field[component.name] = value
3016 def dump(self, indent=0): # pragma: no cover
3017 # lobster-exclude: Debugging feature
3018 self.write_indent(indent, f"Record_Object {self.name}")
3019 self.write_indent(indent + 1, f"Type: {self.n_typ.name}")
3020 for key, value in self.field.items():
3021 self.write_indent(indent + 1, f"Field {key}")
3022 value.dump(indent + 2)
3023 if self.section:
3024 self.section[-1].dump(indent + 1)
3026 def resolve_references(self, mh):
3027 assert isinstance(mh, Message_Handler)
3028 for val in self.field.values():
3029 val.resolve_references(mh)
3031 def perform_checks(self, mh):
3032 # lobster-trace: LRM.Check_Evaluation_Order
3033 # lobster-trace: LRM.Evaluation_Of_Checks
3034 assert isinstance(mh, Message_Handler)
3036 ok = True
3038 # First evaluate all tuple checks
3039 for n_comp in self.n_typ.all_components():
3040 if not n_comp.n_typ.perform_type_checks(mh, 3040 ↛ 3042line 3040 didn't jump to line 3042 because the condition on line 3040 was never true
3041 self.field[n_comp.name]):
3042 ok = False
3044 # Then evaluate all record checks
3045 for check in self.n_typ.iter_checks():
3046 # Prints messages, if applicable. Raises exception on
3047 # fatal checks, which causes this to abort.
3048 if not check.perform(mh, self):
3049 ok = False
3051 return ok
3053 def __repr__(self):
3054 return "%s<%s>" % (self.__class__.__name__,
3055 self.n_package.name + "." +
3056 self.n_typ.name + "." +
3057 self.name)
3060class Section(Entity):
3061 # lobster-trace: LRM.Section_Declaration
3062 """A section for readability
3064 This represents a section construct in TRLC files to group record
3065 objects together::
3067 section "Foo" {
3068 ^^^^^ parent section
3069 section "Bar" {
3070 ^^^^^ section
3072 :attribute parent: the parent section or None
3073 :type: Section
3075 """
3076 def __init__(self, name, location, parent):
3077 super().__init__(name, location)
3078 assert isinstance(parent, Section) or parent is None
3079 self.parent = parent
3081 def dump(self, indent=0): # pragma: no cover
3082 self.write_indent(indent, f"Section {self.name}")
3083 if self.parent is None:
3084 self.write_indent(indent + 1, "Parent: None")
3085 else:
3086 self.write_indent(indent + 1, f"Parent: {self.parent.name}")
3089##############################################################################
3090# Symbol Table & Scopes
3091##############################################################################
3093class Symbol_Table:
3094 """ Symbol table mapping names to entities
3095 """
3096 def __init__(self, parent=None):
3097 # lobster-exclude: Constructor only declares variables
3098 assert isinstance(parent, Symbol_Table) or parent is None
3099 self.parent = parent
3100 self.imported = []
3101 self.table = OrderedDict()
3102 self.trlc_files = []
3103 self.section_names = []
3105 @staticmethod
3106 def simplified_name(name):
3107 # lobster-trace: LRM.Sufficiently_Distinct
3108 assert isinstance(name, str)
3109 return name.lower().replace("_", "")
3111 def all_names(self):
3112 # lobster-exclude: API for users
3113 """ All names in the symbol table
3115 :rtype: set[str]
3116 """
3117 rv = set(item.name for item in self.table.values())
3118 if self.parent:
3119 rv |= self.parent.all_names()
3120 return rv
3122 def iter_record_objects_by_section(self):
3123 """API for users
3125 Retriving information about the section hierarchy for record objects
3126 Inputs: folder with trlc files where trlc files have sections,
3127 sub sections and record objects
3128 Output: Information about sections and level of sections,
3129 record objects and levels of record object
3130 """
3131 for record_object in self.iter_record_objects():
3132 location = record_object.location.file_name
3133 if location not in self.trlc_files: 3133 ↛ 3136line 3133 didn't jump to line 3136 because the condition on line 3133 was always true
3134 self.trlc_files.append(location)
3135 yield location
3136 if record_object.section:
3137 object_level = len(record_object.section) - 1
3138 for level, section in enumerate(record_object.section):
3139 if section not in self.section_names: 3139 ↛ 3138line 3139 didn't jump to line 3138 because the condition on line 3139 was always true
3140 self.section_names.append(section)
3141 yield section.name, level
3142 yield record_object, object_level
3143 else:
3144 object_level = 0
3145 yield record_object, object_level
3147 def iter_record_objects(self):
3148 # lobster-exclude: API for users
3149 """ Iterate over all record objects
3151 :rtype: iterable[Record_Object]
3152 """
3153 for item in self.table.values():
3154 if isinstance(item, Package):
3155 yield from item.symbols.iter_record_objects()
3157 elif isinstance(item, Record_Object):
3158 yield item
3160 def values(self, subtype=None):
3161 # lobster-exclude: API for users
3162 assert subtype is None or isinstance(subtype, type)
3163 if self.parent:
3164 yield from self.parent.values(subtype)
3165 for name in sorted(self.table):
3166 if subtype is None or isinstance(self.table[name], subtype):
3167 yield self.table[name]
3169 def make_visible(self, stab):
3170 assert isinstance(stab, Symbol_Table)
3171 self.imported.append(stab)
3173 def register(self, mh, entity):
3174 # lobster-trace: LRM.Duplicate_Types
3175 # lobster-trace: LRM.Unique_Enumeration_Literals
3176 # lobster-trace: LRM.Tuple_Unique_Field_Names
3177 # lobster-trace: LRM.Sufficiently_Distinct
3178 # lobster-trace: LRM.Unique_Object_Names
3180 assert isinstance(mh, Message_Handler)
3181 assert isinstance(entity, Entity)
3183 simple_name = self.simplified_name(entity.name)
3185 if self.contains_raw(simple_name):
3186 pdef = self.lookup_direct(mh, entity.name, entity.location,
3187 simplified=True)
3188 if pdef.name == entity.name:
3189 mh.error(entity.location,
3190 "duplicate definition, previous definition at %s" %
3191 mh.cross_file_reference(pdef.location))
3192 else:
3193 mh.error(entity.location,
3194 "%s is too similar to %s, declared at %s" %
3195 (entity.name,
3196 pdef.name,
3197 mh.cross_file_reference(pdef.location)))
3199 else:
3200 self.table[simple_name] = entity
3202 def __contains__(self, name):
3203 # lobster-trace: LRM.Described_Name_Equality
3204 return self.contains(name)
3206 def contains_raw(self, simple_name, precise_name=None):
3207 # lobster-trace: LRM.Described_Name_Equality
3208 # lobster-trace: LRM.Sufficiently_Distinct
3209 #
3210 # Internal function to test if the simplified name is in the
3211 # table.
3212 assert isinstance(simple_name, str)
3213 assert isinstance(precise_name, str) or precise_name is None
3215 if simple_name in self.table:
3216 # No need to continue searching since registering a
3217 # clashing name would have been stopped
3218 return precise_name is None or \
3219 self.table[simple_name].name == precise_name
3221 elif self.parent:
3222 return self.parent.contains_raw(simple_name, precise_name)
3224 for stab in self.imported:
3225 if stab.contains_raw(simple_name, precise_name):
3226 return True
3228 return False
3230 def contains(self, name):
3231 # lobster-trace: LRM.Described_Name_Equality
3232 """ Tests if the given name is in the table
3234 :param name: the name to test
3235 :type name: str
3237 :rtype: bool
3238 """
3239 assert isinstance(name, str)
3240 return self.contains_raw(self.simplified_name(name), name)
3242 def lookup_assuming(self, mh, name, required_subclass=None):
3243 # lobster-trace: LRM.Described_Name_Equality
3244 # lobster-trace: LRM.Sufficiently_Distinct
3245 """Retrieve an object from the table assuming its there
3247 This is intended for the API specifically where you want to
3248 e.g. find some used-defined types you know are there.
3250 :param mh: The message handler to use
3251 :type mh: Message_Handler
3253 :param name: The name to search for
3254 :type name: str
3256 :param required_subclass: If set, creates an error if the object \
3257 is not an instance of the given class
3258 :type required_subclass: type
3260 :raise TRLC_Error: if the object is not of the required subclass
3261 :returns: the specified entity (or None if it does not exist)
3262 :rtype: Entity
3264 """
3265 assert isinstance(mh, Message_Handler)
3266 assert isinstance(name, str)
3267 assert isinstance(required_subclass, type) or required_subclass is None
3269 simple_name = self.simplified_name(name)
3271 ptr = self
3272 for ptr in [self] + self.imported: 3272 ↛ 3290line 3272 didn't jump to line 3290 because the loop on line 3272 didn't complete
3273 while ptr: 3273 ↛ 3272line 3273 didn't jump to line 3272 because the condition on line 3273 was always true
3274 if simple_name in ptr.table: 3274 ↛ 3288line 3274 didn't jump to line 3288 because the condition on line 3274 was always true
3275 rv = ptr.table[simple_name]
3276 if rv.name != name: 3276 ↛ 3277line 3276 didn't jump to line 3277 because the condition on line 3276 was never true
3277 return None
3279 if required_subclass is not None and \ 3279 ↛ 3281line 3279 didn't jump to line 3281 because the condition on line 3279 was never true
3280 not isinstance(rv, required_subclass):
3281 mh.error(rv.location,
3282 "%s %s is not a %s" %
3283 (rv.__class__.__name__,
3284 name,
3285 required_subclass.__name__))
3286 return rv
3287 else:
3288 ptr = ptr.parent
3290 return None
3292 def lookup_direct(self,
3293 mh,
3294 name,
3295 error_location,
3296 required_subclass=None,
3297 simplified=False):
3298 # lobster-trace: LRM.Described_Name_Equality
3299 # lobster-trace: LRM.Sufficiently_Distinct
3300 # lobster-trace: LRM.Valid_Base_Names
3301 # lobster-trace: LRM.Valid_Access_Prefixes
3302 # lobster-trace: LRM.Valid_Function_Prefixes
3303 """Retrieve an object from the table
3305 For example::
3307 pkg = stab.lookup_direct(mh,
3308 "potato",
3309 Location("foobar.txt", 42),
3310 Package)
3312 This would search for an object named ``potato``. If it is
3313 found, and it is a package, it is returned. If it is not a
3314 Package, then the following error is issued::
3316 foobar.txt:42: error: Enumeration_Type potato is not a Package
3318 If it is not found at all, then the following error is issued::
3320 foobar.txt:42: error: unknown symbol potato
3322 :param mh: The message handler to use
3323 :type mh: Message_Handler
3325 :param name: The name to search for
3326 :type name: str
3328 :param error_location: Where to create the error if the name is \
3329 not found
3330 :type error_location: Location
3332 :param required_subclass: If set, creates an error if the object \
3333 is not an instance of the given class
3334 :type required_subclass: type
3336 :param simplified: If set, look up the given simplified name instead \
3337 of the actual name
3338 :type simplified: bool
3340 :raise TRLC_Error: if the name is not in the table
3341 :raise TRLC_Error: if the object is not of the required subclass
3342 :returns: the specified entity
3343 :rtype: Entity
3345 """
3346 assert isinstance(mh, Message_Handler)
3347 assert isinstance(name, str)
3348 assert isinstance(error_location, Location)
3349 assert isinstance(required_subclass, type) or required_subclass is None
3350 assert isinstance(simplified, bool)
3352 simple_name = self.simplified_name(name)
3353 ptr = self
3354 options = []
3356 for ptr in [self] + self.imported:
3357 while ptr:
3358 if simple_name in ptr.table:
3359 rv = ptr.table[simple_name]
3360 if not simplified and rv.name != name: 3360 ↛ 3361line 3360 didn't jump to line 3361 because the condition on line 3360 was never true
3361 mh.error(error_location,
3362 "unknown symbol %s, did you mean %s?" %
3363 (name,
3364 rv.name))
3366 if required_subclass is not None and \
3367 not isinstance(rv, required_subclass):
3368 mh.error(error_location,
3369 "%s %s is not a %s" %
3370 (rv.__class__.__name__,
3371 name,
3372 required_subclass.__name__))
3373 return rv
3374 else:
3375 options += list(item.name
3376 for item in ptr.table.values())
3377 ptr = ptr.parent
3379 matches = get_close_matches(
3380 word = name,
3381 possibilities = options,
3382 n = 1)
3384 if matches:
3385 mh.error(error_location,
3386 "unknown symbol %s, did you mean %s?" %
3387 (name,
3388 matches[0]))
3389 else:
3390 mh.error(error_location,
3391 "unknown symbol %s" % name)
3393 def lookup(self, mh, referencing_token, required_subclass=None):
3394 # lobster-trace: LRM.Described_Name_Equality
3395 assert isinstance(mh, Message_Handler)
3396 assert isinstance(referencing_token, Token)
3397 assert referencing_token.kind in ("IDENTIFIER", "BUILTIN")
3398 assert isinstance(required_subclass, type) or required_subclass is None
3400 return self.lookup_direct(
3401 mh = mh,
3402 name = referencing_token.value,
3403 error_location = referencing_token.location,
3404 required_subclass = required_subclass)
3406 def write_indent(self, indent, message): # pragma: no cover
3407 # lobster-exclude: Debugging feature
3408 assert isinstance(indent, int)
3409 assert indent >= 0
3410 assert isinstance(message, str)
3411 print(" " * (3 * indent) + message)
3413 def dump(self, indent=0, omit_heading=False): # pragma: no cover
3414 # lobster-exclude: Debugging feature
3415 if omit_heading:
3416 new_indent = indent
3417 else:
3418 self.write_indent(indent, "Symbol_Table")
3419 new_indent = indent + 1
3420 ptr = self
3421 while ptr:
3422 for name in ptr.table:
3423 ptr.table[name].dump(new_indent)
3424 ptr = ptr.parent
3426 @classmethod
3427 def create_global_table(cls, mh):
3428 # lobster-trace: LRM.Builtin_Types
3429 # lobster-trace: LRM.Builtin_Functions
3430 # lobster-trace: LRM.Builtin_Type_Conversion_Functions
3431 # lobster-trace: LRM.Signature_Len
3432 # lobster-trace: LRM.Signature_String_End_Functions
3433 # lobster-trace: LRM.Signature_Matches
3435 stab = Symbol_Table()
3436 stab.register(mh, Builtin_Integer())
3437 stab.register(mh, Builtin_Decimal())
3438 stab.register(mh, Builtin_Boolean())
3439 stab.register(mh, Builtin_String())
3440 stab.register(mh, Builtin_Markup_String())
3441 stab.register(mh,
3442 Builtin_Function("len", 1))
3443 stab.register(mh,
3444 Builtin_Function("startswith", 2))
3445 stab.register(mh,
3446 Builtin_Function("endswith", 2))
3447 stab.register(mh,
3448 Builtin_Function("matches", 2))
3449 stab.register(mh,
3450 Builtin_Function("oneof", 1, arity_at_least=True))
3452 return stab
3455class Scope:
3456 def __init__(self):
3457 # lobster-exclude: Constructor only declares variables
3458 self.scope = []
3460 def push(self, stab):
3461 assert isinstance(stab, Symbol_Table)
3462 self.scope.append(stab)
3464 def pop(self):
3465 self.scope.pop()
3467 def contains(self, name):
3468 assert isinstance(name, str)
3470 for stab in reversed(self.scope):
3471 if stab.contains(name):
3472 return True
3473 return False
3475 def lookup(self, mh, referencing_token, required_subclass=None):
3476 assert len(self.scope) >= 1
3477 assert isinstance(mh, Message_Handler)
3478 assert isinstance(referencing_token, Token)
3479 assert referencing_token.kind in ("IDENTIFIER", "BUILTIN")
3480 assert isinstance(required_subclass, type) or required_subclass is None
3482 for stab in reversed(self.scope[1:]):
3483 if stab.contains(referencing_token.value):
3484 return stab.lookup(mh, referencing_token, required_subclass)
3485 return self.scope[0].lookup(mh, referencing_token, required_subclass)
3487 def size(self):
3488 return len(self.scope)