Coverage for trlc/ast.py: 90%
1231 statements
« prev ^ index » next coverage.py v7.10.7, created at 2026-01-15 09:56 +0000
« prev ^ index » next coverage.py v7.10.7, created at 2026-01-15 09:56 +0000
1#!/usr/bin/env python3
2#
3# TRLC - Treat Requirements Like Code
4# Copyright (C) 2022-2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
5# Copyright (C) 2024-2025 Florian Schanda
6#
7# This file is part of the TRLC Python Reference Implementation.
8#
9# TRLC is free software: you can redistribute it and/or modify it
10# under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# TRLC is distributed in the hope that it will be useful, but WITHOUT
15# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
17# License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with TRLC. If not, see <https://www.gnu.org/licenses/>.
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, gstab):
373 # lobster-trace: LRM.Check_Messages
374 # lobster-trace: LRM.Check_Severity
375 assert isinstance(mh, Message_Handler)
376 assert isinstance(composite_object, (Record_Object,
377 Tuple_Aggregate))
378 assert isinstance(gstab, Symbol_Table)
380 if isinstance(composite_object, Record_Object):
381 result = self.n_expr.evaluate(mh,
382 copy(composite_object.field),
383 gstab)
384 else:
385 result = self.n_expr.evaluate(mh,
386 copy(composite_object.value),
387 gstab)
388 if result.value is None:
389 loc = self.get_real_location(composite_object)
390 mh.error(loc,
391 "check %s (%s) evaluates to null" %
392 (self.n_expr.to_string(),
393 mh.cross_file_reference(self.location)))
395 assert isinstance(result.value, bool)
397 if not result.value:
398 loc = self.get_real_location(composite_object)
399 if self.severity == "warning":
400 mh.warning(location = loc,
401 message = self.message,
402 explanation = self.extrainfo,
403 user = True)
404 else:
405 mh.error(location = loc,
406 message = self.message,
407 explanation = self.extrainfo,
408 fatal = self.severity == "fatal",
409 user = True)
410 return False
412 return True
414##############################################################################
415# AST Nodes (Expressions)
416##############################################################################
419class Unary_Operator(Enum):
420 # lobster-exclude: Utility enumeration for unary operators
421 MINUS = auto()
422 PLUS = auto()
423 LOGICAL_NOT = auto()
424 ABSOLUTE_VALUE = auto()
426 STRING_LENGTH = auto()
427 ARRAY_LENGTH = auto()
429 CONVERSION_TO_INT = auto()
430 CONVERSION_TO_DECIMAL = auto()
433class Binary_Operator(Enum):
434 # lobster-exclude: Utility enumeration for binary operators
435 LOGICAL_AND = auto() # Short-circuit
436 LOGICAL_OR = auto() # Short-circuit
437 LOGICAL_XOR = auto()
438 LOGICAL_IMPLIES = auto() # Short-circuit
440 COMP_EQ = auto()
441 COMP_NEQ = auto()
442 COMP_LT = auto()
443 COMP_LEQ = auto()
444 COMP_GT = auto()
445 COMP_GEQ = auto()
447 STRING_CONTAINS = auto()
448 STRING_STARTSWITH = auto()
449 STRING_ENDSWITH = auto()
450 STRING_REGEX = auto()
452 ARRAY_CONTAINS = auto()
454 PLUS = auto()
455 MINUS = auto()
456 TIMES = auto()
457 DIVIDE = auto()
458 REMAINDER = auto()
460 POWER = auto()
462 INDEX = auto()
465class Expression(Node, metaclass=ABCMeta):
466 """Abstract base class for all expressions.
468 :attribute typ: The type of this expression (or None for null values)
469 :type: Type
470 """
471 def __init__(self, location, typ):
472 # lobster-exclude: Constructor only declares variables
473 super().__init__(location)
474 assert typ is None or isinstance(typ, Type)
475 self.typ = typ
477 def evaluate(self, mh, context, gstab): # pragma: no cover
478 """Evaluate the expression in the given context
480 The context can be None, in which case the expression is
481 evaluated in a static context. Otherwise it must be a
482 dictionary that maps names (such as record fields or
483 quantified variables) to expressions.
485 The global symbol table must be None (for static context
486 evaluations), otherwise it must contain the global symbol
487 table to resolve record references.
489 :param mh: the message handler to use
490 :type mh: Message_Handler
491 :param context: name mapping or None (for a static context)
492 :type context: dict[str, Expression]
493 :raise TRLC_Error: if the expression cannot be evaluated
494 :return: result of the evaluation
495 :rtype: Value
497 """
498 assert isinstance(mh, Message_Handler)
499 assert context is None or isinstance(context, dict)
500 assert gstab is None or isinstance(gstab, Symbol_Table)
501 assert False, "evaluate not implemented for %s" % \
502 self.__class__.__name__
504 @abstractmethod
505 def to_string(self): # pragma: no cover
506 assert False, "to_string not implemented for %s" % \
507 self.__class__.__name__
509 def ensure_type(self, mh, typ):
510 # lobster-trace: LRM.Restricted_Null
511 # lobster-trace: LRM.Null_Is_Invalid
513 assert isinstance(typ, (type, Type))
514 if self.typ is None:
515 mh.error(self.location,
516 "null is not permitted here")
517 elif isinstance(typ, type) and not isinstance(self.typ, typ):
518 mh.error(self.location,
519 "expected expression of type %s, got %s instead" %
520 (typ.__name__,
521 self.typ.__class__.__name__))
522 elif isinstance(typ, Type) and self.typ != typ:
523 mh.error(self.location,
524 "expected expression of type %s, got %s instead" %
525 (typ.name,
526 self.typ.name))
528 def resolve_references(self, mh):
529 assert isinstance(mh, Message_Handler)
531 @abstractmethod
532 def can_be_null(self):
533 """Test if the expression could return null
535 Checks the expression if it could generate a null value
536 *without* raising an error. For example `x` could generate a
537 null value if `x` is a record component that is
538 optional. However `x + 1` could not, since an error would
539 occur earlier.
541 :return: possibility of encountering null
542 :rtype: bool
544 """
545 assert False, "can_be_null not implemented for %s" % \
546 self.__class__.__name__
549class Implicit_Null(Expression):
550 """Synthesised null values
552 When a record object or tuple aggregate is declared and an
553 optional component or field is not specified, we synthesise an
554 implicit null expression for this.
556 For example given this TRLC type::
558 type T {
559 x optional Integer
560 }
562 And this declaration::
564 T Potato {}
566 Then the field mapping for Potato will be::
568 {x: Implicit_Null}
570 Each field will get its own implicit null. Further note that this
571 implicit null is distinct from the explicit :class:`Null_Literal`
572 that can appear in check expressions.
574 """
575 def __init__(self, composite_object, composite_component):
576 # lobster-trace: LRM.Unspecified_Optional_Components
577 assert isinstance(composite_object, (Record_Object,
578 Tuple_Aggregate))
579 assert isinstance(composite_component, Composite_Component)
580 super().__init__(composite_object.location, None)
582 def to_string(self):
583 return "null"
585 def evaluate(self, mh, context, gstab):
586 # lobster-trace: LRM.Unspecified_Optional_Components
587 assert isinstance(mh, Message_Handler)
588 assert context is None or isinstance(context, dict)
589 assert gstab is None or isinstance(gstab, Symbol_Table)
590 return Value(self.location, None, None)
592 def to_python_object(self):
593 return None
595 def dump(self, indent=0): # pragma: no cover
596 # lobster-exclude: Debugging feature
597 self.write_indent(indent, "Implicit_Null")
599 def can_be_null(self):
600 return True
603class Literal(Expression, metaclass=ABCMeta):
604 """Abstract base for all Literals
606 Does not offer any additional features, but it's a nice way to
607 group together all literal types. This is useful if you want to
608 check if you are dealing with a literal::
610 isinstance(my_expression, Literal)
612 """
613 @abstractmethod
614 def to_python_object(self):
615 assert False
618class Null_Literal(Literal):
619 # lobster-trace: LRM.Primary
620 """The null literal
622 This can appear in check expressions::
624 a /= null implies a > 5
625 ^^^^
627 Please note that this is distinct from the :class:`Implicit_Null`
628 values that appear in record objects.
630 """
631 def __init__(self, token):
632 assert isinstance(token, Token)
633 assert token.kind == "KEYWORD"
634 assert token.value == "null"
635 super().__init__(token.location, None)
637 def dump(self, indent=0): # pragma: no cover
638 self.write_indent(indent, "Null Literal")
640 def to_string(self):
641 return "null"
643 def evaluate(self, mh, context, gstab):
644 assert isinstance(mh, Message_Handler)
645 assert context is None or isinstance(context, dict)
646 assert gstab is None or isinstance(gstab, Symbol_Table)
647 return Value(self.location, None, None)
649 def to_python_object(self):
650 return None
652 def can_be_null(self):
653 return True
656class Integer_Literal(Literal):
657 # lobster-trace: LRM.Integer_Values
658 # lobster-trace: LRM.Primary
659 """Integer literals
661 Note that these are always positive. A negative integer is
662 actually a unary negation expression, operating on a positive
663 integer literal::
665 x == -5
667 This would create the following tree::
669 OP_EQUALITY
670 NAME_REFERENCE x
671 UNARY_EXPRESSION -
672 INTEGER_LITERAL 5
674 :attribute value: the non-negative integer value
675 :type: int
676 """
677 def __init__(self, token, typ):
678 assert isinstance(token, Token)
679 assert token.kind == "INTEGER"
680 assert isinstance(typ, Builtin_Integer)
681 super().__init__(token.location, typ)
683 self.value = token.value
685 def dump(self, indent=0): # pragma: no cover
686 self.write_indent(indent, f"Integer Literal {self.value}")
688 def to_string(self):
689 return str(self.value)
691 def evaluate(self, mh, context, gstab):
692 assert isinstance(mh, Message_Handler)
693 assert context is None or isinstance(context, dict)
694 assert gstab is None or isinstance(gstab, Symbol_Table)
695 return Value(self.location, self.value, self.typ)
697 def to_python_object(self):
698 return self.value
700 def can_be_null(self):
701 return False
704class Decimal_Literal(Literal):
705 # lobster-trace: LRM.Decimal_Values
706 # lobster-trace: LRM.Primary
707 """Decimal literals
709 Note that these are always positive. A negative decimal is
710 actually a unary negation expression, operating on a positive
711 decimal literal::
713 x == -5.0
715 This would create the following tree::
717 OP_EQUALITY
718 NAME_REFERENCE x
719 UNARY_EXPRESSION -
720 DECIMAL_LITERAL 5.0
722 :attribute value: the non-negative decimal value
723 :type: fractions.Fraction
724 """
725 def __init__(self, token, typ):
726 assert isinstance(token, Token)
727 assert token.kind == "DECIMAL"
728 assert isinstance(typ, Builtin_Decimal)
729 super().__init__(token.location, typ)
731 self.value = token.value
733 def dump(self, indent=0): # pragma: no cover
734 self.write_indent(indent, f"Decimal Literal {self.value}")
736 def to_string(self):
737 return str(self.value)
739 def evaluate(self, mh, context, gstab):
740 assert isinstance(mh, Message_Handler)
741 assert context is None or isinstance(context, dict)
742 assert gstab is None or isinstance(gstab, Symbol_Table)
743 return Value(self.location, self.value, self.typ)
745 def to_python_object(self):
746 return float(self.value)
748 def can_be_null(self):
749 return False
752class String_Literal(Literal):
753 # lobster-trace: LRM.String_Values
754 # lobster-trace: LRM.Markup_String_Values
755 # lobster-trace: LRM.Primary
756 """String literals
758 Note the value of the string does not include the quotation marks,
759 and any escape sequences are fully resolved. For example::
761 "foo\\"bar"
763 Will have a value of ``foo"bar``.
765 :attribute value: string content
766 :type: str
768 :attribute references: resolved references of a markup string
769 :type: list[Record_Reference]
771 """
772 def __init__(self, token, typ):
773 assert isinstance(token, Token)
774 assert token.kind == "STRING"
775 assert isinstance(typ, Builtin_String)
776 super().__init__(token.location, typ)
778 self.value = token.value
779 self.has_references = isinstance(typ, Builtin_Markup_String)
780 self.references = []
782 def dump(self, indent=0): # pragma: no cover
783 self.write_indent(indent, f"String Literal {repr(self.value)}")
784 if self.has_references:
785 self.write_indent(indent + 1, "Markup References")
786 for ref in self.references:
787 ref.dump(indent + 2)
789 def to_string(self):
790 return self.value
792 def evaluate(self, mh, context, gstab):
793 assert isinstance(mh, Message_Handler)
794 assert context is None or isinstance(context, dict)
795 assert gstab is None or isinstance(gstab, Symbol_Table)
796 return Value(self.location, self.value, self.typ)
798 def to_python_object(self):
799 return self.value
801 def resolve_references(self, mh):
802 assert isinstance(mh, Message_Handler)
803 for ref in self.references:
804 ref.resolve_references(mh)
806 def can_be_null(self):
807 return False
810class Boolean_Literal(Literal):
811 # lobster-trace: LRM.Boolean_Values
812 # lobster-trace: LRM.Primary
813 """Boolean values
815 :attribute value: the boolean value
816 :type: bool
817 """
818 def __init__(self, token, typ):
819 assert isinstance(token, Token)
820 assert token.kind == "KEYWORD"
821 assert token.value in ("false", "true")
822 assert isinstance(typ, Builtin_Boolean)
823 super().__init__(token.location, typ)
825 self.value = token.value == "true"
827 def dump(self, indent=0): # pragma: no cover
828 self.write_indent(indent, f"Boolean Literal {self.value}")
830 def to_string(self):
831 return str(self.value)
833 def evaluate(self, mh, context, gstab):
834 assert isinstance(mh, Message_Handler)
835 assert context is None or isinstance(context, dict)
836 assert gstab is None or isinstance(gstab, Symbol_Table)
837 return Value(self.location, self.value, self.typ)
839 def to_python_object(self):
840 return self.value
842 def can_be_null(self):
843 return False
846class Enumeration_Literal(Literal):
847 """Enumeration values
849 Note that this is distinct from
850 :class:`Enumeration_Literal_Spec`. An enumeration literal is a
851 specific mention of an enumeration member in an expression::
853 foo != my_enum.POTATO
854 ^^^^^^^^^^^^^^
856 To get to the string value of the enumeration literal
857 (i.e. ``POTATO`` here) you can get the name of the literal spec
858 itself: ``enum_lit.value.name``; and to get the name of the
859 enumeration (i.e. ``my_enum`` here) you can use
860 ``enum_lit.value.n_typ.name``.
862 :attribute value: enumeration value
863 :type: Enumeration_Literal_Spec
865 """
866 def __init__(self, location, literal):
867 # lobster-exclude: Constructor only declares variables
868 assert isinstance(literal, Enumeration_Literal_Spec)
869 super().__init__(location, literal.n_typ)
871 self.value = literal
873 def dump(self, indent=0): # pragma: no cover
874 # lobster-exclude: Debugging feature
875 self.write_indent(indent,
876 f"Enumeration Literal {self.typ.name}.{self.value.name}")
878 def to_string(self):
879 return self.typ.name + "." + self.value.name
881 def evaluate(self, mh, context, gstab):
882 assert isinstance(mh, Message_Handler)
883 assert context is None or isinstance(context, dict)
884 assert gstab is None or isinstance(gstab, Symbol_Table)
885 return Value(self.location, self.value, self.typ)
887 def to_python_object(self):
888 return self.value.name
890 def can_be_null(self):
891 return False
894class Array_Aggregate(Expression):
895 """Instances of array types
897 This is created when assigning to array components::
899 potatoes = ["picasso", "yukon gold", "sweet"]
900 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
902 The type of expression that can be found in an array is somewhat
903 limited:
905 * :class:`Literal`
906 * :class:`Array_Aggregate`
907 * :class:`Record_Reference`
909 :attribute value: contents of the array
910 :type: list[Expression]
912 """
913 def __init__(self, location, typ):
914 # lobster-trace: LRM.Record_Object_Declaration
916 super().__init__(location, typ)
917 self.value = []
919 def dump(self, indent=0): # pragma: no cover
920 # lobster-exclude: Debugging feature
921 self.write_indent(indent, "Array_Aggregate")
922 for n_value in self.value:
923 n_value.dump(indent + 1)
925 def append(self, value):
926 assert isinstance(value, (Literal,
927 Unary_Expression,
928 Array_Aggregate,
929 Tuple_Aggregate,
930 Record_Reference))
931 self.value.append(value)
933 def to_string(self):
934 return "[" + ", ".join(x.to_string() for x in self.value) + "]"
936 def evaluate(self, mh, context, gstab):
937 assert isinstance(mh, Message_Handler)
938 assert context is None or isinstance(context, dict)
939 assert gstab is None or isinstance(gstab, Symbol_Table)
940 return Value(self.location,
941 list(element.evaluate(mh, context, gstab)
942 for element in self.value),
943 self.typ)
945 def resolve_references(self, mh):
946 assert isinstance(mh, Message_Handler)
948 for val in self.value:
949 val.resolve_references(mh)
951 def to_python_object(self):
952 return [x.to_python_object() for x in self.value]
954 def can_be_null(self):
955 return False
958class Tuple_Aggregate(Expression):
959 """Instances of a tuple
961 This is created when assigning to a tuple components. There are
962 two forms, the ordinary form::
964 coordinate = (12.3, 40.0)
965 ^^^^^^^^^^^^
967 And the separator form::
969 item = 12345@42
970 ^^^^^^^^
972 In terms of AST there is no difference, as the separator is only
973 syntactic sugar.
975 :attribute value: contents of the tuple
976 :type: dict[str, Expression]
978 """
979 def __init__(self, location, typ):
980 # lobster-trace: LRM.Unspecified_Optional_Components
981 # lobster-trace: LRM.Record_Object_Declaration
983 super().__init__(location, typ)
984 self.value = {n_field.name : Implicit_Null(self, n_field)
985 for n_field in self.typ.components.values()}
987 def assign(self, field, value):
988 assert isinstance(field, str)
989 assert isinstance(value, (Literal,
990 Unary_Expression,
991 Tuple_Aggregate,
992 Record_Reference)), \
993 "value is %s" % value.__class__.__name__
994 assert field in self.typ.components
996 self.value[field] = value
998 def dump(self, indent=0): # pragma: no cover
999 # lobster-exclude: Debugging feature
1000 self.write_indent(indent, "Tuple_Aggregate")
1001 self.write_indent(indent + 1, f"Type: {self.typ.name}")
1002 for n_item in self.typ.iter_sequence():
1003 if isinstance(n_item, Composite_Component):
1004 self.value[n_item.name].dump(indent + 1)
1006 def to_string(self):
1007 first = True
1008 if self.typ.has_separators():
1009 rv = ""
1010 else:
1011 rv = "("
1012 for n_item in self.typ.iter_sequence():
1013 if isinstance(n_item, Separator):
1014 rv += " %s " % n_item.token.value
1015 elif first:
1016 first = False
1017 else:
1018 rv += ", "
1020 if isinstance(n_item, Composite_Component):
1021 rv += self.value[n_item.name].to_string()
1022 if self.typ.has_separators():
1023 rv = ""
1024 else:
1025 rv = ")"
1026 return rv
1028 def evaluate(self, mh, context, gstab):
1029 assert isinstance(mh, Message_Handler)
1030 assert context is None or isinstance(context, dict)
1031 assert gstab is None or isinstance(gstab, Symbol_Table)
1032 return Value(self.location,
1033 {name : element.evaluate(mh, context, gstab)
1034 for name, element in self.value.items()},
1035 self.typ)
1037 def resolve_references(self, mh):
1038 assert isinstance(mh, Message_Handler)
1040 for val in self.value.values():
1041 val.resolve_references(mh)
1043 def to_python_object(self):
1044 return {name: value.to_python_object()
1045 for name, value in self.value.items()}
1047 def can_be_null(self):
1048 return False
1051class Record_Reference(Expression):
1052 """Reference to another record object
1054 This can appear in record object declarations::
1056 Requirement Kitten {
1057 depends_on = Other_Package.Cat
1058 ^1 ^2
1059 }
1061 Note that this is distinct from :class:`Record_Object`. It is just
1062 the name; to get to the object referred to by this you can consult
1063 the target attribute.
1065 The reason we have this indirection is that not all names can be
1066 immediately resolved on parsing in the TRLC language.
1068 Note that while the containing package (see 1) is optional in the
1069 source language, the containing package will always be filled in
1070 in this AST node.
1072 :attribute name: The name of the record (see 2)
1073 :type: str
1075 :attribute target: The concrete record object referred to by (2)
1076 :type: Record_Object
1078 :attribute package: The package (see 1) supposed to contain (2)
1079 :type: Package
1081 """
1082 def __init__(self, location, name, typ, package):
1083 # lobster-exclude: Constructor only declares variables
1084 assert isinstance(location, Location)
1085 assert isinstance(name, str)
1086 assert isinstance(typ, Record_Type) or typ is None
1087 assert isinstance(package, Package)
1088 super().__init__(location, typ)
1090 self.name = name
1091 self.target = None
1092 self.package = package
1094 def dump(self, indent=0): # pragma: no cover
1095 # lobster-exclude: Debugging feature
1096 self.write_indent(indent, f"Record Reference {self.name}")
1097 self.write_indent(indent + 1,
1098 f"Resolved: {self.target is not None}")
1100 def to_string(self):
1101 return self.name
1103 def evaluate(self, mh, context, gstab):
1104 assert isinstance(mh, Message_Handler)
1105 assert context is None or isinstance(context, dict)
1106 assert gstab is None or isinstance(gstab, Symbol_Table)
1107 return Value(self.location, copy(self.target.field), self.typ)
1109 def resolve_references(self, mh):
1110 # lobster-trace: LRM.References_To_Extensions
1111 assert isinstance(mh, Message_Handler)
1113 self.target = self.package.symbols.lookup_direct(
1114 mh = mh,
1115 name = self.name,
1116 error_location = self.location,
1117 required_subclass = Record_Object)
1118 if self.typ is None:
1119 self.typ = self.target.n_typ
1120 elif not self.target.n_typ.is_subclass_of(self.typ):
1121 mh.error(self.location,
1122 "expected reference of type %s, but %s is of type %s" %
1123 (self.typ.name,
1124 self.target.name,
1125 self.target.n_typ.name))
1127 def to_python_object(self):
1128 return self.target.fully_qualified_name()
1130 def can_be_null(self):
1131 return False
1134class Name_Reference(Expression):
1135 # lobster-trace: LRM.Qualified_Name
1136 # lobster-trace: LRM.Static_Regular_Expression
1138 """Reference to a name
1140 Name reference to either a :class:`Composite_Component` or a
1141 :class:`Quantified_Variable`. The actual value of course depends
1142 on the context. See :py:meth:`Expression.evaluate()`.
1144 For example::
1146 (forall x in potato => x > 1)
1147 ^1 ^2
1149 Both indicated parts are a :class:`Name_Reference`, the first one
1150 refers to a :class:`Composite_Component`, and the second refers to a
1151 :class:`Quantified_Variable`.
1153 :attribute entity: the entity named here
1154 :type: Composite_Component, Quantified_Variable
1155 """
1156 def __init__(self, location, entity):
1157 assert isinstance(entity, (Composite_Component,
1158 Quantified_Variable))
1159 super().__init__(location, entity.n_typ)
1160 self.entity = entity
1162 def dump(self, indent=0): # pragma: no cover
1163 self.write_indent(indent, f"Name Reference to {self.entity.name}")
1165 def to_string(self):
1166 return self.entity.name
1168 def evaluate(self, mh, context, gstab):
1169 assert isinstance(mh, Message_Handler)
1170 assert context is None or isinstance(context, dict)
1171 assert gstab is None or isinstance(gstab, Symbol_Table)
1173 if context is None:
1174 mh.error(self.location,
1175 "cannot be used in a static context")
1177 assert self.entity.name in context
1178 return context[self.entity.name].evaluate(mh, context, gstab)
1180 def can_be_null(self):
1181 # The only way we could generate null here (without raising
1182 # error earlier) is when we refer to a component that is
1183 # optional.
1184 if isinstance(self.entity, Composite_Component):
1185 return self.entity.optional
1186 else:
1187 return False
1190class Unary_Expression(Expression):
1191 """Expression with only one operand
1193 This captures the following operations:
1195 * Unary_Operator.PLUS (e.g. ``+5``)
1196 * Unary_Operator.MINUS (e.g. ``-5``)
1197 * Unary_Operator.ABSOLUTE_VALUE (e.g. ``abs 42``)
1198 * Unary_Operator.LOGICAL_NOT (e.g. ``not True``)
1199 * Unary_Operator.STRING_LENGTH (e.g. ``len("foobar")``)
1200 * Unary_Operator.ARRAY_LENGTH (e.g. ``len(component_name)``)
1201 * Unary_Operator.CONVERSION_TO_INT (e.g. ``Integer(5.3)``)
1202 * Unary_Operator.CONVERSION_TO_DECIMAL (e.g. ``Decimal(5)``)
1204 Note that several builtin functions are mapped to unary operators.
1206 :attribute operator: the operation
1207 :type: Unary_Operator
1209 :attribute n_operand: the expression we operate on
1210 :type: Expression
1212 """
1213 def __init__(self, mh, location, typ, operator, n_operand):
1214 # lobster-trace: LRM.Simple_Expression
1215 # lobster-trace: LRM.Relation
1216 # lobster-trace: LRM.Factor
1217 # lobster-trace: LRM.Signature_Len
1218 # lobster-trace: LRM.Signature_Type_Conversion
1220 super().__init__(location, typ)
1221 assert isinstance(mh, Message_Handler)
1222 assert isinstance(operator, Unary_Operator)
1223 assert isinstance(n_operand, Expression)
1224 self.operator = operator
1225 self.n_operand = n_operand
1227 if operator in (Unary_Operator.MINUS,
1228 Unary_Operator.PLUS,
1229 Unary_Operator.ABSOLUTE_VALUE):
1230 self.n_operand.ensure_type(mh, Builtin_Numeric_Type)
1231 elif operator == Unary_Operator.LOGICAL_NOT:
1232 self.n_operand.ensure_type(mh, Builtin_Boolean)
1233 elif operator == Unary_Operator.STRING_LENGTH:
1234 self.n_operand.ensure_type(mh, Builtin_String)
1235 elif operator == Unary_Operator.ARRAY_LENGTH:
1236 self.n_operand.ensure_type(mh, Array_Type)
1237 elif operator == Unary_Operator.CONVERSION_TO_INT:
1238 self.n_operand.ensure_type(mh, Builtin_Numeric_Type)
1239 elif operator == Unary_Operator.CONVERSION_TO_DECIMAL:
1240 self.n_operand.ensure_type(mh, Builtin_Numeric_Type)
1241 else:
1242 mh.ice_loc(self.location,
1243 "unexpected unary operation %s" % operator)
1245 def to_string(self):
1246 prefix_operators = {
1247 Unary_Operator.MINUS : "-",
1248 Unary_Operator.PLUS : "+",
1249 Unary_Operator.ABSOLUTE_VALUE : "abs ",
1250 Unary_Operator.LOGICAL_NOT : "not ",
1251 }
1252 function_calls = {
1253 Unary_Operator.STRING_LENGTH : "len",
1254 Unary_Operator.ARRAY_LENGTH : "len",
1255 Unary_Operator.CONVERSION_TO_INT : "Integer",
1256 Unary_Operator.CONVERSION_TO_DECIMAL : "Decimal"
1257 }
1259 if self.operator in prefix_operators:
1260 return prefix_operators[self.operator] + \
1261 self.n_operand.to_string()
1263 elif self.operator in function_calls:
1264 return "%s(%s)" % (function_calls[self.operator],
1265 self.n_operand.to_string())
1267 else:
1268 assert False
1270 def dump(self, indent=0): # pragma: no cover
1271 # lobster-exclude: Debugging feature
1272 self.write_indent(indent, f"Unary {self.operator} Expression")
1273 self.write_indent(indent + 1, f"Type: {self.typ.name}")
1274 self.n_operand.dump(indent + 1)
1276 def evaluate(self, mh, context, gstab):
1277 # lobster-trace: LRM.Null_Is_Invalid
1278 # lobster-trace: LRM.Signature_Len
1279 # lobster-trace: LRM.Signature_Type_Conversion
1280 # lobster-trace: LRM.Len_Semantics
1281 # lobster-trace: LRM.Integer_Conversion_Semantics
1282 # lobster-trace: LRM.Decimal_Conversion_Semantics
1284 assert isinstance(mh, Message_Handler)
1285 assert context is None or isinstance(context, dict)
1286 assert gstab is None or isinstance(gstab, Symbol_Table)
1288 v_operand = self.n_operand.evaluate(mh, context, gstab)
1289 if v_operand.value is None: 1289 ↛ 1290line 1289 didn't jump to line 1290 because the condition on line 1289 was never true
1290 mh.error(v_operand.location,
1291 "input to unary expression %s (%s) must not be null" %
1292 (self.to_string(),
1293 mh.cross_file_reference(self.location)))
1295 if self.operator == Unary_Operator.MINUS:
1296 return Value(location = self.location,
1297 value = -v_operand.value,
1298 typ = self.typ)
1299 elif self.operator == Unary_Operator.PLUS:
1300 return Value(location = self.location,
1301 value = +v_operand.value,
1302 typ = self.typ)
1303 elif self.operator == Unary_Operator.LOGICAL_NOT:
1304 return Value(location = self.location,
1305 value = not v_operand.value,
1306 typ = self.typ)
1307 elif self.operator == Unary_Operator.ABSOLUTE_VALUE:
1308 return Value(location = self.location,
1309 value = abs(v_operand.value),
1310 typ = self.typ)
1311 elif self.operator in (Unary_Operator.STRING_LENGTH,
1312 Unary_Operator.ARRAY_LENGTH):
1313 return Value(location = self.location,
1314 value = len(v_operand.value),
1315 typ = self.typ)
1316 elif self.operator == Unary_Operator.CONVERSION_TO_INT:
1317 if isinstance(v_operand.value, Fraction): 1317 ↛ 1323line 1317 didn't jump to line 1323 because the condition on line 1317 was always true
1318 return Value(
1319 location = self.location,
1320 value = math.round_nearest_away(v_operand.value),
1321 typ = self.typ)
1322 else:
1323 return Value(location = self.location,
1324 value = v_operand.value,
1325 typ = self.typ)
1326 elif self.operator == Unary_Operator.CONVERSION_TO_DECIMAL:
1327 return Value(location = self.location,
1328 value = Fraction(v_operand.value),
1329 typ = self.typ)
1330 else:
1331 mh.ice_loc(self.location,
1332 "unexpected unary operation %s" % self.operator)
1334 def to_python_object(self):
1335 assert self.operator in (Unary_Operator.MINUS,
1336 Unary_Operator.PLUS)
1337 val = self.n_operand.to_python_object()
1338 if self.operator == Unary_Operator.MINUS:
1339 return -val
1340 else:
1341 return val
1343 def can_be_null(self):
1344 return False
1347class Binary_Expression(Expression):
1348 """Expression with two operands
1350 This captures the following operations:
1352 * Binary_Operator.LOGICAL_AND (e.g. ``a and b``)
1353 * Binary_Operator.LOGICAL_OR (e.g. ``a or b``)
1354 * Binary_Operator.LOGICAL_XOR (e.g. ``a xor b``)
1355 * Binary_Operator.LOGICAL_IMPLIES (e.g. ``a implies b``)
1356 * Binary_Operator.COMP_EQ (e.g. ``a == null``)
1357 * Binary_Operator.COMP_NEQ (e.g. ``a != null``)
1358 * Binary_Operator.COMP_LT (e.g. ``1 < 2``)
1359 * Binary_Operator.COMP_LEQ (e.g. ``1 <= 2``)
1360 * Binary_Operator.COMP_GT (e.g. ``a > b``)
1361 * Binary_Operator.COMP_GEQ (e.g. ``a >= b``)
1362 * Binary_Operator.STRING_CONTAINS (e.g. ``"foo" in "foobar"``)
1363 * Binary_Operator.STRING_STARTSWITH (e.g. ``startswith("foo", "f")``)
1364 * Binary_Operator.STRING_ENDSWITH (e.g. ``endswith("foo", "o")``)
1365 * Binary_Operator.STRING_REGEX (e.g. ``matches("foo", ".o.``)
1366 * Binary_Operator.ARRAY_CONTAINS (e.g. ``42 in arr``)
1367 * Binary_Operator.PLUS (e.g. ``42 + b`` or ``"foo" + bar``)
1368 * Binary_Operator.MINUS (e.g. ``a - 1``)
1369 * Binary_Operator.TIMES (e.g. ``2 * x``)
1370 * Binary_Operator.DIVIDE (e.g. ``x / 2``)
1371 * Binary_Operator.REMAINDER (e.g. ``x % 2``)
1372 * Binary_Operator.POWER (e.g. ``x ** 2``)
1373 * Binary_Operator.INDEX (e.g. ``foo[2]``)
1375 Note that several builtin functions are mapped to unary operators.
1377 Note also that the plus operation is supported for integers,
1378 rationals and strings.
1380 :attribute operator: the operation
1381 :type: Binary_Operator
1383 :attribute n_lhs: the first operand
1384 :type: Expression
1386 :attribute n_rhs: the second operand
1387 :type: Expression
1389 """
1390 def __init__(self, mh, location, typ, operator, n_lhs, n_rhs):
1391 # lobster-trace: LRM.Expression
1392 # lobster-trace: LRM.Relation
1393 # lobster-trace: LRM.Simple_Expression
1394 # lobster-trace: LRM.Term
1395 # lobster-trace: LRM.Factor
1396 # lobster-trace: LRM.Signature_String_End_Functions
1397 # lobster-trace: LRM.Signature_Matches
1399 super().__init__(location, typ)
1400 assert isinstance(mh, Message_Handler)
1401 assert isinstance(operator, Binary_Operator)
1402 assert isinstance(n_lhs, Expression)
1403 assert isinstance(n_rhs, Expression)
1404 self.operator = operator
1405 self.n_lhs = n_lhs
1406 self.n_rhs = n_rhs
1408 if operator in (Binary_Operator.LOGICAL_AND,
1409 Binary_Operator.LOGICAL_OR,
1410 Binary_Operator.LOGICAL_XOR,
1411 Binary_Operator.LOGICAL_IMPLIES):
1412 self.n_lhs.ensure_type(mh, Builtin_Boolean)
1413 self.n_rhs.ensure_type(mh, Builtin_Boolean)
1415 elif operator in (Binary_Operator.COMP_EQ,
1416 Binary_Operator.COMP_NEQ):
1417 if (self.n_lhs.typ is None) or (self.n_rhs.typ is None):
1418 # We can compary anything to null (including itself)
1419 pass
1420 elif self.n_lhs.typ != self.n_rhs.typ:
1421 # Otherwise we can compare anything, as long as the
1422 # types match
1423 mh.error(self.location,
1424 "type mismatch: %s and %s do not match" %
1425 (self.n_lhs.typ.name,
1426 self.n_rhs.typ.name))
1428 elif operator in (Binary_Operator.COMP_LT,
1429 Binary_Operator.COMP_LEQ,
1430 Binary_Operator.COMP_GT,
1431 Binary_Operator.COMP_GEQ):
1432 self.n_lhs.ensure_type(mh, Builtin_Numeric_Type)
1433 self.n_rhs.ensure_type(mh, self.n_lhs.typ)
1435 elif operator in (Binary_Operator.STRING_CONTAINS,
1436 Binary_Operator.STRING_STARTSWITH,
1437 Binary_Operator.STRING_ENDSWITH,
1438 Binary_Operator.STRING_REGEX):
1439 self.n_lhs.ensure_type(mh, Builtin_String)
1440 self.n_rhs.ensure_type(mh, Builtin_String)
1442 elif operator == Binary_Operator.ARRAY_CONTAINS:
1443 self.n_rhs.ensure_type(mh, Array_Type)
1444 self.n_lhs.ensure_type(mh, self.n_rhs.typ.element_type.__class__)
1446 elif operator == Binary_Operator.PLUS:
1447 if isinstance(self.n_lhs.typ, Builtin_Numeric_Type):
1448 self.n_rhs.ensure_type(mh, self.n_lhs.typ)
1449 else:
1450 self.n_lhs.ensure_type(mh, Builtin_String)
1451 self.n_rhs.ensure_type(mh, Builtin_String)
1453 elif operator in (Binary_Operator.MINUS,
1454 Binary_Operator.TIMES,
1455 Binary_Operator.DIVIDE):
1456 self.n_lhs.ensure_type(mh, Builtin_Numeric_Type)
1457 self.n_rhs.ensure_type(mh, self.n_lhs.typ)
1459 elif operator == Binary_Operator.POWER:
1460 self.n_lhs.ensure_type(mh, Builtin_Numeric_Type)
1461 self.n_rhs.ensure_type(mh, Builtin_Integer)
1463 elif operator == Binary_Operator.REMAINDER:
1464 self.n_lhs.ensure_type(mh, Builtin_Integer)
1465 self.n_rhs.ensure_type(mh, Builtin_Integer)
1467 elif operator == Binary_Operator.INDEX:
1468 self.n_lhs.ensure_type(mh, Array_Type)
1469 self.n_rhs.ensure_type(mh, Builtin_Integer)
1471 else:
1472 mh.ice_loc(self.location,
1473 "unexpected binary operation %s" % operator)
1475 def dump(self, indent=0): # pragma: no cover
1476 # lobster-exclude: Debugging feature
1477 self.write_indent(indent, f"Binary {self.operator} Expression")
1478 self.write_indent(indent + 1, f"Type: {self.typ.name}")
1479 self.n_lhs.dump(indent + 1)
1480 self.n_rhs.dump(indent + 1)
1482 def to_string(self):
1483 infix_operators = {
1484 Binary_Operator.LOGICAL_AND : "and",
1485 Binary_Operator.LOGICAL_OR : "or",
1486 Binary_Operator.LOGICAL_XOR : "xor",
1487 Binary_Operator.LOGICAL_IMPLIES : "implies",
1488 Binary_Operator.COMP_EQ : "==",
1489 Binary_Operator.COMP_NEQ : "!=",
1490 Binary_Operator.COMP_LT : "<",
1491 Binary_Operator.COMP_LEQ : "<=",
1492 Binary_Operator.COMP_GT : ">",
1493 Binary_Operator.COMP_GEQ : ">=",
1494 Binary_Operator.STRING_CONTAINS : "in",
1495 Binary_Operator.ARRAY_CONTAINS : "in",
1496 Binary_Operator.PLUS : "+",
1497 Binary_Operator.MINUS : "-",
1498 Binary_Operator.TIMES : "*",
1499 Binary_Operator.DIVIDE : "/",
1500 Binary_Operator.REMAINDER : "%",
1501 Binary_Operator.POWER : "**",
1502 }
1503 string_functions = {
1504 Binary_Operator.STRING_STARTSWITH : "startswith",
1505 Binary_Operator.STRING_ENDSWITH : "endswith",
1506 Binary_Operator.STRING_REGEX : "matches",
1507 }
1509 if self.operator in infix_operators:
1510 return "%s %s %s" % (self.n_lhs.to_string(),
1511 infix_operators[self.operator],
1512 self.n_rhs.to_string())
1514 elif self.operator in string_functions:
1515 return "%s(%s, %s)" % (string_functions[self.operator],
1516 self.n_lhs.to_string(),
1517 self.n_rhs.to_string())
1519 elif self.operator == Binary_Operator.INDEX:
1520 return "%s[%s]" % (self.n_lhs.to_string(),
1521 self.n_rhs.to_string())
1523 else:
1524 assert False
1526 def evaluate(self, mh, context, gstab):
1527 # lobster-trace: LRM.Null_Equivalence
1528 # lobster-trace: LRM.Null_Is_Invalid
1529 # lobster-trace: LRM.Signature_String_End_Functions
1530 # lobster-trace: LRM.Signature_Matches
1531 # lobster-trace: LRM.Startswith_Semantics
1532 # lobster-trace: LRM.Endswith_Semantics
1533 # lobster-trace: LRM.Matches_Semantics
1535 assert isinstance(mh, Message_Handler)
1536 assert context is None or isinstance(context, dict)
1537 assert gstab is None or isinstance(gstab, Symbol_Table)
1539 v_lhs = self.n_lhs.evaluate(mh, context, gstab)
1540 if v_lhs.value is None and \
1541 self.operator not in (Binary_Operator.COMP_EQ,
1542 Binary_Operator.COMP_NEQ):
1543 mh.error(v_lhs.location,
1544 "lhs of check %s (%s) must not be null" %
1545 (self.to_string(),
1546 mh.cross_file_reference(self.location)))
1548 # Check for the short-circuit operators first
1549 if self.operator == Binary_Operator.LOGICAL_AND:
1550 assert isinstance(v_lhs.value, bool)
1551 if v_lhs.value:
1552 return self.n_rhs.evaluate(mh, context, gstab)
1553 else:
1554 return v_lhs
1556 elif self.operator == Binary_Operator.LOGICAL_OR:
1557 assert isinstance(v_lhs.value, bool)
1558 if v_lhs.value:
1559 return v_lhs
1560 else:
1561 return self.n_rhs.evaluate(mh, context, gstab)
1563 elif self.operator == Binary_Operator.LOGICAL_IMPLIES:
1564 assert isinstance(v_lhs.value, bool)
1565 if v_lhs.value:
1566 return self.n_rhs.evaluate(mh, context, gstab)
1567 else:
1568 return Value(location = self.location,
1569 value = True,
1570 typ = self.typ)
1572 # Otherwise, evaluate RHS and do the operation
1573 v_rhs = self.n_rhs.evaluate(mh, context, gstab)
1574 if v_rhs.value is None and \
1575 self.operator not in (Binary_Operator.COMP_EQ,
1576 Binary_Operator.COMP_NEQ):
1577 mh.error(v_rhs.location,
1578 "rhs of check %s (%s) must not be null" %
1579 (self.to_string(),
1580 mh.cross_file_reference(self.location)))
1582 if self.operator == Binary_Operator.LOGICAL_XOR:
1583 assert isinstance(v_lhs.value, bool)
1584 assert isinstance(v_rhs.value, bool)
1585 return Value(location = self.location,
1586 value = v_lhs.value ^ v_rhs.value,
1587 typ = self.typ)
1589 elif self.operator == Binary_Operator.COMP_EQ:
1590 return Value(location = self.location,
1591 value = v_lhs.value == v_rhs.value,
1592 typ = self.typ)
1594 elif self.operator == Binary_Operator.COMP_NEQ:
1595 return Value(location = self.location,
1596 value = v_lhs.value != v_rhs.value,
1597 typ = self.typ)
1599 elif self.operator in (Binary_Operator.COMP_LT,
1600 Binary_Operator.COMP_LEQ,
1601 Binary_Operator.COMP_GT,
1602 Binary_Operator.COMP_GEQ):
1603 return Value(
1604 location = self.location,
1605 value = {
1606 Binary_Operator.COMP_LT : lambda lhs, rhs: lhs < rhs,
1607 Binary_Operator.COMP_LEQ : lambda lhs, rhs: lhs <= rhs,
1608 Binary_Operator.COMP_GT : lambda lhs, rhs: lhs > rhs,
1609 Binary_Operator.COMP_GEQ : lambda lhs, rhs: lhs >= rhs,
1610 }[self.operator](v_lhs.value, v_rhs.value),
1611 typ = self.typ)
1613 elif self.operator == Binary_Operator.STRING_CONTAINS:
1614 assert isinstance(v_lhs.value, str)
1615 assert isinstance(v_rhs.value, str)
1617 return Value(location = self.location,
1618 value = v_lhs.value in v_rhs.value,
1619 typ = self.typ)
1621 elif self.operator == Binary_Operator.STRING_STARTSWITH:
1622 assert isinstance(v_lhs.value, str)
1623 assert isinstance(v_rhs.value, str)
1624 return Value(location = self.location,
1625 value = v_lhs.value.startswith(v_rhs.value),
1626 typ = self.typ)
1628 elif self.operator == Binary_Operator.STRING_ENDSWITH:
1629 assert isinstance(v_lhs.value, str)
1630 assert isinstance(v_rhs.value, str)
1631 return Value(location = self.location,
1632 value = v_lhs.value.endswith(v_rhs.value),
1633 typ = self.typ)
1635 elif self.operator == Binary_Operator.STRING_REGEX:
1636 assert isinstance(v_lhs.value, str)
1637 assert isinstance(v_rhs.value, str)
1638 return Value(location = self.location,
1639 value = re.match(v_rhs.value,
1640 v_lhs.value) is not None,
1641 typ = self.typ)
1643 elif self.operator == Binary_Operator.ARRAY_CONTAINS:
1644 assert isinstance(v_rhs.value, list)
1646 return Value(location = self.location,
1647 value = v_lhs in v_rhs.value,
1648 typ = self.typ)
1650 elif self.operator == Binary_Operator.PLUS:
1651 assert isinstance(v_lhs.value, (int, str, Fraction))
1652 assert isinstance(v_rhs.value, (int, str, Fraction))
1653 return Value(location = self.location,
1654 value = v_lhs.value + v_rhs.value,
1655 typ = self.typ)
1657 elif self.operator == Binary_Operator.MINUS:
1658 assert isinstance(v_lhs.value, (int, Fraction))
1659 assert isinstance(v_rhs.value, (int, Fraction))
1660 return Value(location = self.location,
1661 value = v_lhs.value - v_rhs.value,
1662 typ = self.typ)
1664 elif self.operator == Binary_Operator.TIMES:
1665 assert isinstance(v_lhs.value, (int, Fraction))
1666 assert isinstance(v_rhs.value, (int, Fraction))
1667 return Value(location = self.location,
1668 value = v_lhs.value * v_rhs.value,
1669 typ = self.typ)
1671 elif self.operator == Binary_Operator.DIVIDE:
1672 assert isinstance(v_lhs.value, (int, Fraction))
1673 assert isinstance(v_rhs.value, (int, Fraction))
1675 if v_rhs.value == 0: 1675 ↛ 1676line 1675 didn't jump to line 1676 because the condition on line 1675 was never true
1676 mh.error(v_rhs.location,
1677 "division by zero in %s (%s)" %
1678 (self.to_string(),
1679 mh.cross_file_reference(self.location)))
1681 if isinstance(v_lhs.value, int):
1682 return Value(location = self.location,
1683 value = v_lhs.value // v_rhs.value,
1684 typ = self.typ)
1685 else:
1686 return Value(location = self.location,
1687 value = v_lhs.value / v_rhs.value,
1688 typ = self.typ)
1690 elif self.operator == Binary_Operator.REMAINDER:
1691 assert isinstance(v_lhs.value, int)
1692 assert isinstance(v_rhs.value, int)
1694 if v_rhs.value == 0: 1694 ↛ 1695line 1694 didn't jump to line 1695 because the condition on line 1694 was never true
1695 mh.error(v_rhs.location,
1696 "division by zero in %s (%s)" %
1697 (self.to_string(),
1698 mh.cross_file_reference(self.location)))
1700 return Value(location = self.location,
1701 value = math.remainder(v_lhs.value, v_rhs.value),
1702 typ = self.typ)
1704 elif self.operator == Binary_Operator.POWER:
1705 assert isinstance(v_lhs.value, (int, Fraction))
1706 assert isinstance(v_rhs.value, int)
1707 return Value(location = self.location,
1708 value = v_lhs.value ** v_rhs.value,
1709 typ = self.typ)
1711 elif self.operator == Binary_Operator.INDEX:
1712 assert isinstance(v_lhs.value, list)
1713 assert isinstance(v_rhs.value, int)
1715 if v_rhs.value < 0: 1715 ↛ 1716line 1715 didn't jump to line 1716 because the condition on line 1715 was never true
1716 mh.error(v_rhs.location,
1717 "index cannot be less than zero in %s (%s)" %
1718 (self.to_string(),
1719 mh.cross_file_reference(self.location)))
1720 elif v_lhs.typ.upper_bound is not None and \ 1720 ↛ 1722line 1720 didn't jump to line 1722 because the condition on line 1720 was never true
1721 v_rhs.value > v_lhs.typ.upper_bound:
1722 mh.error(v_rhs.location,
1723 "index cannot be more than %u in %s (%s)" %
1724 (v_lhs.typ.upper_bound,
1725 self.to_string(),
1726 mh.cross_file_reference(self.location)))
1727 elif v_rhs.value > len(v_lhs.value): 1727 ↛ 1728line 1727 didn't jump to line 1728 because the condition on line 1727 was never true
1728 mh.error(v_lhs.location,
1729 "array is not big enough in %s (%s)" %
1730 (self.to_string(),
1731 mh.cross_file_reference(self.location)))
1733 return Value(location = self.location,
1734 value = v_lhs.value[v_rhs.value].value,
1735 typ = self.typ)
1737 else:
1738 mh.ice_loc(self.location,
1739 "unexpected binary operator %s" % self.operator)
1741 def can_be_null(self):
1742 return False
1745class Field_Access_Expression(Expression):
1746 """Tuple or Record field access
1748 For example in::
1750 foo.bar
1751 ^1 ^2
1753 :attribute n_prefix: expression with tuple or record type (see 1)
1754 :type: Expression
1756 :attribute n_field: a tuple field to dereference (see 2)
1757 :type: Composite_Component
1759 """
1760 def __init__(self, mh, location, n_prefix, n_field):
1761 assert isinstance(mh, Message_Handler)
1762 assert isinstance(n_prefix, Expression)
1763 assert isinstance(n_field, Composite_Component)
1764 super().__init__(location, n_field.n_typ)
1765 self.n_prefix = n_prefix
1766 self.n_field = n_field
1768 self.n_prefix.ensure_type(mh, self.n_field.member_of)
1770 def dump(self, indent=0): # pragma: no cover
1771 # lobster-exclude: Debugging feature
1772 self.write_indent(indent, f"Field_Access ({self.n_field.name})")
1773 self.n_prefix.dump(indent + 1)
1775 def to_string(self):
1776 return self.n_prefix.to_string() + "." + self.n_field.name
1778 def evaluate(self, mh, context, gstab):
1779 assert isinstance(mh, Message_Handler)
1780 assert context is None or isinstance(context, dict)
1781 assert gstab is None or isinstance(gstab, Symbol_Table)
1783 v_prefix = self.n_prefix.evaluate(mh,
1784 context,
1785 gstab).value
1786 if v_prefix is None:
1787 # lobster-trace: LRM.Dereference
1788 mh.error(self.n_prefix.location,
1789 "null dereference")
1791 v_field = v_prefix[self.n_field.name]
1792 if isinstance(v_field, Implicit_Null):
1793 return v_field.evaluate(mh, context, gstab)
1794 else:
1795 return v_field
1797 def can_be_null(self):
1798 return False
1801class Range_Test(Expression):
1802 """Range membership test
1804 For example in::
1806 x in 1 .. field+1
1807 ^lhs ^lower ^^^^^^^upper
1809 Note that none of these are guaranteed to be literals or names;
1810 you can have arbitrarily complex expressions here.
1812 :attribute n_lhs: the expression to test
1813 :type: Expression
1815 :attribute n_lower: the lower bound
1816 :type: Expression
1818 :attribute n_upper: the upper bound
1819 :type: Expression
1821 """
1822 def __init__(self, mh, location, typ, n_lhs, n_lower, n_upper):
1823 # lobster-trace: LRM.Relation
1824 super().__init__(location, typ)
1825 assert isinstance(mh, Message_Handler)
1826 assert isinstance(n_lhs, Expression)
1827 assert isinstance(n_lower, Expression)
1828 assert isinstance(n_upper, Expression)
1829 self.n_lhs = n_lhs
1830 self.n_lower = n_lower
1831 self.n_upper = n_upper
1833 self.n_lhs.ensure_type(mh, Builtin_Numeric_Type)
1834 self.n_lower.ensure_type(mh, self.n_lhs.typ)
1835 self.n_upper.ensure_type(mh, self.n_lhs.typ)
1837 def to_string(self):
1838 return "%s in %s .. %s" % (self.n_lhs.to_string(),
1839 self.n_lower.to_string(),
1840 self.n_upper.to_string())
1842 def dump(self, indent=0): # pragma: no cover
1843 # lobster-exclude: Debugging feature
1844 self.write_indent(indent, "Range Test")
1845 self.write_indent(indent + 1, f"Type: {self.typ}")
1846 self.n_lhs.dump(indent + 1)
1847 self.n_lower.dump(indent + 1)
1848 self.n_upper.dump(indent + 1)
1850 def evaluate(self, mh, context, gstab):
1851 # lobster-trace: LRM.Null_Is_Invalid
1852 assert isinstance(mh, Message_Handler)
1853 assert context is None or isinstance(context, dict)
1854 assert gstab is None or isinstance(gstab, Symbol_Table)
1856 v_lhs = self.n_lhs.evaluate(mh, context, gstab)
1857 if v_lhs.value is None: 1857 ↛ 1858line 1857 didn't jump to line 1858 because the condition on line 1857 was never true
1858 mh.error(v_lhs.location,
1859 "lhs of range check %s (%s) see must not be null" %
1860 (self.to_string(),
1861 mh.cross_file_reference(self.location)))
1863 v_lower = self.n_lower.evaluate(mh, context, gstab)
1864 if v_lower.value is None: 1864 ↛ 1865line 1864 didn't jump to line 1865 because the condition on line 1864 was never true
1865 mh.error(v_lower.location,
1866 "lower bound of range check %s (%s) must not be null" %
1867 (self.to_string(),
1868 mh.cross_file_reference(self.location)))
1870 v_upper = self.n_upper.evaluate(mh, context, gstab)
1871 if v_upper.value is None: 1871 ↛ 1872line 1871 didn't jump to line 1872 because the condition on line 1871 was never true
1872 mh.error(v_upper.location,
1873 "upper bound of range check %s (%s) must not be null" %
1874 (self.to_string(),
1875 mh.cross_file_reference(self.location)))
1877 return Value(location = self.location,
1878 value = v_lower.value <= v_lhs.value <= v_upper.value,
1879 typ = self.typ)
1881 def can_be_null(self):
1882 return False
1885class OneOf_Expression(Expression):
1886 """OneOf expression
1888 For example in::
1890 oneof(a, b, c)
1891 ^^^^^^^ choices
1893 :attribute choices: a list of boolean expressions to test
1894 :type: list[Expression]
1895 """
1896 def __init__(self, mh, location, typ, choices):
1897 # lobster-trace: LRM.Signature_OneOf
1898 super().__init__(location, typ)
1899 assert isinstance(typ, Builtin_Boolean)
1900 assert isinstance(mh, Message_Handler)
1901 assert isinstance(choices, list)
1902 assert all(isinstance(item, Expression)
1903 for item in choices)
1904 self.choices = choices
1906 for n_choice in choices:
1907 n_choice.ensure_type(mh, Builtin_Boolean)
1909 def to_string(self):
1910 return "oneof(%s)" % ", ".join(n_choice.to_string()
1911 for n_choice in self.choices)
1913 def dump(self, indent=0): # pragma: no cover
1914 # lobster-exclude: Debugging feature
1915 self.write_indent(indent, "OneOf Test")
1916 self.write_indent(indent + 1, f"Type: {self.typ}")
1917 for n_choice in self.choices:
1918 n_choice.dump(indent + 1)
1920 def evaluate(self, mh, context, gstab):
1921 # lobster-trace: LRM.OneOf_Semantics
1922 assert isinstance(mh, Message_Handler)
1923 assert context is None or isinstance(context, dict)
1924 assert gstab is None or isinstance(gstab, Symbol_Table)
1926 v_choices = [n_choice.evaluate(mh, context, gstab).value
1927 for n_choice in self.choices]
1929 return Value(location = self.location,
1930 value = v_choices.count(True) == 1,
1931 typ = self.typ)
1933 def can_be_null(self):
1934 return False
1937class Action(Node):
1938 """An if or elseif part inside a conditional expression
1940 Each :class:`Conditional_Expression` is made up of a sequence of
1941 Actions. For example here is a single expression with two
1942 Actions::
1944 (if x == 0 then "zero" elsif x == 1 then "one" else "lots")
1945 ^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^
1947 Note that the else part is not an action, it is an attribute of
1948 the :class:`Conditional_Expression` itself.
1950 :attribute kind: Either if or elseif
1951 :type: str
1953 :attribute n_cond: The boolean condition expression
1954 :type: Expression
1956 :attribute n_expr: The value if the condition evaluates to true
1957 :type: Expression
1959 """
1960 def __init__(self, mh, t_kind, n_condition, n_expression):
1961 # lobster-trace: LRM.Conditional_Expression
1962 assert isinstance(mh, Message_Handler)
1963 assert isinstance(t_kind, Token)
1964 assert t_kind.kind == "KEYWORD"
1965 assert t_kind.value in ("if", "elsif")
1966 assert isinstance(n_condition, Expression)
1967 assert isinstance(n_expression, Expression)
1968 super().__init__(t_kind.location)
1969 self.kind = t_kind.value
1970 self.n_cond = n_condition
1971 self.n_expr = n_expression
1972 # lobster-trace: LRM.Conditional_Expression_Types
1973 self.n_cond.ensure_type(mh, Builtin_Boolean)
1975 def dump(self, indent=0): # pragma: no cover
1976 # lobster-exclude: Debugging feature
1977 self.write_indent(indent, f"{self.kind.capitalize()} Action")
1978 self.write_indent(indent + 1, "Condition")
1979 self.n_cond.dump(indent + 2)
1980 self.write_indent(indent + 1, "Value")
1981 self.n_expr.dump(indent + 2)
1983 def to_string(self):
1984 return "%s %s then %s" % (self.kind,
1985 self.n_cond.to_string(),
1986 self.n_expr.to_string())
1989class Conditional_Expression(Expression):
1990 """A conditional expression
1992 Each :class:`Conditional_Expression` is made up of a sequence of
1993 one or more :class:`Action`. For example here is a single
1994 expression with two Actions::
1996 (if x == 0 then "zero" elsif x == 1 then "one" else "lots")
1997 ^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^
1999 The else expression is part of the conditional expression itself.
2001 A conditional expression will have at least one action (the if
2002 action), and all other actions will be elsif actions. The else
2003 expression is not optional and will always be present. The types
2004 of all actions and the else expression will match.
2006 :attribute actions: a list of Actions
2007 :type: list[Action]
2009 :attribute else_expr: the else expression
2010 :type: Expression
2012 """
2013 def __init__(self, location, if_action):
2014 # lobster-trace: LRM.Conditional_Expression
2015 assert isinstance(if_action, Action)
2016 assert if_action.kind == "if"
2017 super().__init__(location, if_action.n_expr.typ)
2018 self.actions = [if_action]
2019 self.else_expr = None
2021 def add_elsif(self, mh, n_action):
2022 # lobster-trace: LRM.Conditional_Expression
2023 # lobster-trace; LRM.Conditional_Expression_Types
2024 assert isinstance(mh, Message_Handler)
2025 assert isinstance(n_action, Action)
2026 assert n_action.kind == "elsif"
2028 n_action.n_expr.ensure_type(mh, self.typ)
2029 self.actions.append(n_action)
2031 def set_else_part(self, mh, n_expr):
2032 # lobster-trace: LRM.Conditional_Expression
2033 # lobster-trace; LRM.Conditional_Expression_Types
2034 assert isinstance(mh, Message_Handler)
2035 assert isinstance(n_expr, Expression)
2037 n_expr.ensure_type(mh, self.typ)
2038 self.else_expr = n_expr
2040 def dump(self, indent=0): # pragma: no cover
2041 # lobster-exclude: Debugging feature
2042 self.write_indent(indent, "Conditional expression")
2043 for action in self.actions:
2044 action.dump(indent + 1)
2045 self.write_indent(indent + 1, "Else")
2046 self.else_expr.dump(indent + 2)
2048 def to_string(self):
2049 rv = "(" + " ".join(action.to_string()
2050 for action in self.actions)
2051 rv += " else %s" % self.else_expr.to_string()
2052 rv += ")"
2053 return rv
2055 def evaluate(self, mh, context, gstab):
2056 # lobster-trace: LRM.Conditional_Expression_Else
2057 # lobster-trace: LRM.Conditional_Expression_Evaluation
2058 # lobster-trace: LRM.Null_Is_Invalid
2059 assert isinstance(mh, Message_Handler)
2060 assert context is None or isinstance(context, dict)
2061 assert gstab is None or isinstance(gstab, Symbol_Table)
2063 for action in self.actions:
2064 v_cond = action.n_cond.evaluate(mh, context, gstab)
2065 if v_cond.value is None: 2065 ↛ 2066line 2065 didn't jump to line 2066 because the condition on line 2065 was never true
2066 mh.error(v_cond.location,
2067 "condition of %s (%s) must not be null" %
2068 (action.to_string(),
2069 mh.cross_file_reference(self.location)))
2070 if v_cond.value:
2071 return action.n_expr.evaluate(mh, context, gstab)
2073 return self.else_expr.evaluate(mh, context, gstab)
2075 def can_be_null(self):
2076 if self.else_expr and self.else_expr.can_be_null():
2077 return True
2079 return any(action.n_expr.can_be_null()
2080 for action in self.actions)
2083class Quantified_Expression(Expression):
2084 """A quantified expression
2086 For example::
2088 (forall x in array_component => x > 0)
2089 ^4 ^1 ^2 ^^^^^3
2091 A quantified expression introduces and binds a
2092 :class:`Quantified_Variable` (see 1) from a specified source (see
2093 2). When the body (see 3) is evaluated, the name of 1 is bound to
2094 each component of the source in turn.
2096 :attribute n_var: The quantified variable (see 1)
2097 :type: Quantified_Variable
2099 :attribute n_source: The array to iterate over (see 2)
2100 :type: Name_Reference
2102 :attribute n_expr: The body of the quantifier (see 3)
2103 :type: Expression
2105 :attribute universal: True means forall, false means exists (see 4)
2106 :type: Boolean
2108 """
2109 def __init__(self, mh, location,
2110 typ,
2111 universal,
2112 n_variable,
2113 n_source,
2114 n_expr):
2115 # lobster-trace: LRM.Quantified_Expression
2116 # lobster-trace: LRM.Quantification_Type
2117 super().__init__(location, typ)
2118 assert isinstance(typ, Builtin_Boolean)
2119 assert isinstance(universal, bool)
2120 assert isinstance(n_variable, Quantified_Variable)
2121 assert isinstance(n_expr, Expression)
2122 assert isinstance(n_source, Name_Reference)
2123 self.universal = universal
2124 self.n_var = n_variable
2125 self.n_expr = n_expr
2126 self.n_source = n_source
2127 self.n_expr.ensure_type(mh, Builtin_Boolean)
2129 def dump(self, indent=0): # pragma: no cover
2130 # lobster-exclude: Debugging feature
2131 if self.universal:
2132 self.write_indent(indent, "Universal quantified expression")
2133 else:
2134 self.write_indent(indent, "Existential quantified expression")
2135 self.n_var.dump(indent + 1)
2136 self.n_expr.dump(indent + 1)
2138 def to_string(self):
2139 return "(%s %s in %s => %s)" % ("forall"
2140 if self.universal
2141 else "exists",
2142 self.n_var.name,
2143 self.n_source.to_string(),
2144 self.n_expr.to_string())
2146 def evaluate(self, mh, context, gstab):
2147 # lobster-trace: LRM.Null_Is_Invalid
2148 # lobster-trace: LRM.Universal_Quantification_Semantics
2149 # lobster-trace: LRM.Existential_Quantification_Semantics
2150 assert isinstance(mh, Message_Handler)
2151 assert context is None or isinstance(context, dict)
2152 assert gstab is None or isinstance(gstab, Symbol_Table)
2154 if context is None: 2154 ↛ 2155line 2154 didn't jump to line 2155 because the condition on line 2154 was never true
2155 new_ctx = {}
2156 else:
2157 new_ctx = copy(context)
2159 # This is going to be a bit tricky. We essentially eliminate
2160 # the quantifier and substitute; for the sake of making better
2161 # error messages.
2162 assert isinstance(self.n_source.entity, Composite_Component)
2163 array_values = context[self.n_source.entity.name]
2164 if isinstance(array_values, Implicit_Null):
2165 mh.error(array_values.location,
2166 "%s in quantified expression %s (%s) "
2167 "must not be null" %
2168 (self.n_source.to_string(),
2169 self.to_string(),
2170 mh.cross_file_reference(self.location)))
2171 else:
2172 assert isinstance(array_values, Array_Aggregate)
2174 rv = self.universal
2175 loc = self.location
2176 for binding in array_values.value:
2177 new_ctx[self.n_var.name] = binding
2178 result = self.n_expr.evaluate(mh, new_ctx, gstab)
2179 assert isinstance(result.value, bool)
2180 if self.universal and not result.value:
2181 rv = False
2182 loc = binding.location
2183 break
2184 elif not self.universal and result.value:
2185 rv = True
2186 loc = binding.location
2187 break
2189 return Value(location = loc,
2190 value = rv,
2191 typ = self.typ)
2193 def can_be_null(self):
2194 return False
2197##############################################################################
2198# AST Nodes (Entities)
2199##############################################################################
2201class Entity(Node, metaclass=ABCMeta):
2202 """Base class for all entities.
2204 An entity is a concrete object (with a name) for which we need to
2205 allocate memory. Examples of entities are types and record
2206 objects.
2208 :attribute name: unqualified name of the entity
2209 :type: str
2211 """
2212 def __init__(self, name, location):
2213 # lobster-trace: LRM.Described_Name_Equality
2214 super().__init__(location)
2215 assert isinstance(name, str)
2216 self.name = name
2219class Typed_Entity(Entity, metaclass=ABCMeta):
2220 """Base class for entities with a type.
2222 A typed entity is a concrete object (with a name and TRLC type)
2223 for which we need to allocate memory. Examples of typed entities
2224 are record objects and components.
2226 :attribute n_typ: type of the entity
2227 :type: Type
2229 """
2230 def __init__(self, name, location, n_typ):
2231 # lobster-exclude: Constructor only declares variables
2232 super().__init__(name, location)
2233 assert isinstance(n_typ, Type)
2234 self.n_typ = n_typ
2237class Quantified_Variable(Typed_Entity):
2238 """Variable used in quantified expression.
2240 A quantified expression declares and binds a variable, for which
2241 we need a named entity. For example in::
2243 (forall x in array => x > 1)
2244 ^
2246 We represent this first x as a :class:`Quantified_Variable`, the
2247 second x will be an ordinary :class:`Name_Reference`.
2249 :attribute typ: type of the variable (i.e. element type of the array)
2250 :type: Type
2252 """
2253 def dump(self, indent=0): # pragma: no cover
2254 # lobster-exclude: Debugging feature
2255 self.write_indent(indent, f"Quantified Variable {self.name}")
2256 self.n_typ.dump(indent + 1)
2259class Type(Entity, metaclass=ABCMeta):
2260 """Abstract base class for all types.
2262 """
2263 def perform_type_checks(self, mh, value, gstab):
2264 assert isinstance(mh, Message_Handler)
2265 assert isinstance(value, Expression)
2266 assert isinstance(gstab, Symbol_Table)
2267 return True
2269 def get_example_value(self):
2270 # lobster-exclude: utility method
2271 assert False
2274class Concrete_Type(Type, metaclass=ABCMeta):
2275 # lobster-trace: LRM.Type_Declarations
2276 """Abstract base class for all non-anonymous types.
2278 :attribute n_package: package where this type was declared
2279 :type: Package
2280 """
2281 def __init__(self, name, location, n_package):
2282 super().__init__(name, location)
2283 assert isinstance(n_package, Package)
2284 self.n_package = n_package
2286 def fully_qualified_name(self):
2287 """Return the FQN for this type (i.e. PACKAGE.NAME)
2289 :returns: the type's full name
2290 :rtype: str
2291 """
2292 return self.n_package.name + "." + self.name
2294 def __hash__(self):
2295 return hash((self.n_package.name, self.name))
2297 def __repr__(self):
2298 return "%s<%s>" % (self.__class__.__name__,
2299 self.fully_qualified_name())
2302class Builtin_Type(Type, metaclass=ABCMeta):
2303 # lobster-trace: LRM.Builtin_Types
2304 """Abstract base class for all builtin types.
2306 """
2307 LOCATION = Location(file_name = "<builtin>")
2309 def __init__(self, name):
2310 super().__init__(name, Builtin_Type.LOCATION)
2312 def dump(self, indent=0): # pragma: no cover
2313 self.write_indent(indent, self.__class__.__name__)
2316class Builtin_Numeric_Type(Builtin_Type, metaclass=ABCMeta):
2317 # lobster-trace: LRM.Builtin_Types
2318 """Abstract base class for all builtin numeric types.
2320 """
2321 def dump(self, indent=0): # pragma: no cover
2322 self.write_indent(indent, self.__class__.__name__)
2325class Builtin_Function(Entity):
2326 # lobster-trace: LRM.Builtin_Functions
2327 """Builtin functions.
2329 These are auto-generated by the :class:`~trlc.trlc.Source_Manager`.
2331 :attribute arity: number of parameters
2332 :type: int
2334 :attribute arity_at_least: when true, arity indicates a lower bound
2335 :type: bool
2337 """
2338 LOCATION = Location(file_name = "<builtin>")
2340 def __init__(self, name, arity, arity_at_least=False):
2341 super().__init__(name, Builtin_Function.LOCATION)
2342 assert isinstance(arity, int)
2343 assert isinstance(arity_at_least, bool)
2344 assert arity >= 0
2345 self.arity = arity
2346 self.arity_at_least = arity_at_least
2348 def dump(self, indent=0): # pragma: no cover
2349 self.write_indent(indent, self.__class__.__name__ + " " + self.name)
2352class Array_Type(Type):
2353 """Anonymous array type.
2355 These are declared implicitly for each record component that has
2356 an array specifier::
2358 foo Integer [5 .. *]
2359 ^
2361 :attribute lower_bound: minimum number of elements
2362 :type: int
2364 :attribute loc_lower: text location of the lower bound indicator
2365 :type: Location
2367 :attribute upper_bound: maximum number of elements (or None)
2368 :type: int
2370 :attribute loc_upper: text location of the upper bound indicator
2371 :type: Location
2373 :attribute element_type: type of the array elements
2374 :type: Type
2376 """
2377 def __init__(self,
2378 location,
2379 element_type,
2380 loc_lower,
2381 lower_bound,
2382 loc_upper,
2383 upper_bound):
2384 # lobster-exclude: Constructor only declares variables
2385 assert isinstance(element_type, Type) or element_type is None
2386 assert isinstance(lower_bound, int)
2387 assert lower_bound >= 0
2388 assert upper_bound is None or isinstance(upper_bound, int)
2389 assert upper_bound is None or upper_bound >= 0
2390 assert isinstance(loc_lower, Location)
2391 assert isinstance(loc_upper, Location)
2393 if element_type is None: 2393 ↛ 2394line 2393 didn't jump to line 2394 because the condition on line 2393 was never true
2394 name = "universal array"
2395 elif upper_bound is None:
2396 if lower_bound == 0:
2397 name = "array of %s" % element_type.name
2398 else:
2399 name = "array of at least %u %s" % (lower_bound,
2400 element_type.name)
2401 elif lower_bound == upper_bound:
2402 name = "array of %u %s" % (lower_bound,
2403 element_type.name)
2404 else:
2405 name = "array of %u to %u %s" % (lower_bound,
2406 upper_bound,
2407 element_type.name)
2408 super().__init__(name, location)
2409 self.lower_bound = lower_bound
2410 self.loc_lower = loc_lower
2411 self.upper_bound = upper_bound
2412 self.loc_upper = loc_upper
2413 self.element_type = element_type
2415 def dump(self, indent=0): # pragma: no cover
2416 # lobster-exclude: Debugging feature
2417 self.write_indent(indent, "Array_Type")
2418 self.write_indent(indent + 1, f"Lower bound: {self.lower_bound}")
2419 if self.upper_bound is None:
2420 self.write_indent(indent + 1, "Upper bound: *")
2421 else:
2422 self.write_indent(indent + 1, f"Upper bound: {self.upper_bound}")
2423 self.write_indent(indent + 1, f"Element type: {self.element_type.name}")
2425 def perform_type_checks(self, mh, value, gstab):
2426 assert isinstance(mh, Message_Handler)
2427 assert isinstance(gstab, Symbol_Table)
2429 if isinstance(value, Array_Aggregate):
2430 return all(self.element_type.perform_type_checks(mh, v, gstab)
2431 for v in value.value)
2432 else:
2433 assert isinstance(value, Implicit_Null)
2434 return True
2436 def get_example_value(self):
2437 # lobster-exclude: utility method
2438 return "[%s]" % self.element_type.get_example_value()
2441class Builtin_Integer(Builtin_Numeric_Type):
2442 # lobster-trace: LRM.Builtin_Types
2443 # lobster-trace: LRM.Integer_Values
2444 """Builtin integer type."""
2445 def __init__(self):
2446 super().__init__("Integer")
2448 def get_example_value(self):
2449 # lobster-exclude: utility method
2450 return "100"
2453class Builtin_Decimal(Builtin_Numeric_Type):
2454 # lobster-trace: LRM.Builtin_Types
2455 # lobster-trace: LRM.Decimal_Values
2456 """Builtin decimal type."""
2457 def __init__(self):
2458 super().__init__("Decimal")
2460 def get_example_value(self):
2461 # lobster-exclude: utility method
2462 return "3.14"
2465class Builtin_Boolean(Builtin_Type):
2466 # lobster-trace: LRM.Builtin_Types
2467 # lobster-trace: LRM.Boolean_Values
2468 """Builtin boolean type."""
2469 def __init__(self):
2470 super().__init__("Boolean")
2472 def get_example_value(self):
2473 # lobster-exclude: utility method
2474 return "true"
2477class Builtin_String(Builtin_Type):
2478 # lobster-trace: LRM.Builtin_Types
2479 # lobster-trace: LRM.String_Values
2480 """Builtin string type."""
2481 def __init__(self):
2482 super().__init__("String")
2484 def get_example_value(self):
2485 # lobster-exclude: utility method
2486 return "\"potato\""
2489class Builtin_Markup_String(Builtin_String):
2490 # lobster-trace: LRM.Builtin_Types
2491 # lobster-trace: LRM.Markup_String_Values
2492 """Builtin string type that allows checked references to TRLC
2493 objects.
2494 """
2495 def __init__(self):
2496 super().__init__()
2497 self.name = "Markup_String"
2499 def get_example_value(self):
2500 # lobster-exclude: utility method
2501 return "\"also see [[potato]]\""
2504class Package(Entity):
2505 """Packages.
2507 A package is declared when it is first encountered (in either a
2508 rsl or trlc file). A package contains all symbols declared in it,
2509 both types and record objects. A package is not associated with
2510 just a single file, it can be spread over multiple files.
2512 :attribute declared_late: indicates if this package is declared in a \
2513 trlc file
2514 :type: bool
2516 :attribute symbols: symbol table of the package
2517 :type: Symbol_Table
2519 """
2520 def __init__(self, name, location, builtin_stab, declared_late):
2521 # lobster-exclude: Constructor only declares variables
2522 super().__init__(name, location)
2523 assert isinstance(builtin_stab, Symbol_Table)
2524 assert isinstance(declared_late, bool)
2525 self.symbols = Symbol_Table()
2526 self.symbols.make_visible(builtin_stab)
2527 self.declared_late = declared_late
2529 def dump(self, indent=0): # pragma: no cover
2530 # lobster-exclude: Debugging feature
2531 self.write_indent(indent, f"Package {self.name}")
2532 self.write_indent(indent + 1, f"Declared_Late: {self.declared_late}")
2533 self.symbols.dump(indent + 1, omit_heading=True)
2535 def __repr__(self):
2536 return "%s<%s>" % (self.__class__.__name__,
2537 self.name)
2540class Composite_Type(Concrete_Type, metaclass=ABCMeta):
2541 """Abstract base for record and tuple types, as they share some
2542 functionality.
2544 :attribute components: type components (including inherited if applicable)
2545 :type: Symbol_Table[Composite_Component]
2547 :attribute description: user-supplied description of the type or None
2548 :type: str
2550 :attribute checks: used-defined checks for this type (excluding \
2551 inherited checks)
2552 :type: list[Check]
2554 """
2555 def __init__(self,
2556 name,
2557 description,
2558 location,
2559 package,
2560 inherited_symbols=None):
2561 # lobster-trace: LRM.Described_Name_Description
2562 super().__init__(name, location, package)
2563 assert isinstance(description, str) or description is None
2564 assert isinstance(inherited_symbols, Symbol_Table) or \
2565 inherited_symbols is None
2567 self.components = Symbol_Table(inherited_symbols)
2568 self.description = description
2569 self.checks = []
2571 def add_check(self, n_check):
2572 # lobster-trace: LRM.Check_Evaluation_Order
2573 assert isinstance(n_check, Check)
2574 self.checks.append(n_check)
2576 def iter_checks(self):
2577 # lobster-trace: LRM.Check_Evaluation_Order
2578 yield from self.checks
2580 def all_components(self):
2581 # lobster-exclude: Convenience function
2582 """Convenience function to get a list of all components.
2584 :rtype: list[Composite_Component]
2585 """
2586 return list(self.components.table.values())
2589class Composite_Component(Typed_Entity):
2590 """Component in a record or tuple.
2592 When declaring a composite type, for each component an entity is
2593 declared::
2595 type|tuple T {
2596 foo "blah" optional Boolean
2597 ^1 ^2 ^3 ^4
2599 :attribute description: optional text (see 2) for this component, or None
2600 :type: str
2602 :attribute member_of: a link back to the containing record or tuple; \
2603 for inherited fields this refers back to the original base record type
2604 :type: Composite_Type
2606 :attribute optional: indicates if the component can be null or not (see 3)
2607 :type: bool
2609 """
2611 def __init__(self,
2612 name,
2613 description,
2614 location,
2615 member_of,
2616 n_typ,
2617 optional):
2618 # lobster-trace: LRM.Described_Name_Description
2619 super().__init__(name, location, n_typ)
2620 assert isinstance(description, str) or description is None
2621 assert isinstance(member_of, Composite_Type)
2622 assert isinstance(optional, bool)
2623 self.description = description
2624 self.member_of = member_of
2625 self.optional = optional
2627 def dump(self, indent=0): # pragma: no cover
2628 # lobster-exclude: Debugging feature
2629 self.write_indent(indent, f"Composite_Component {self.name}")
2630 if self.description:
2631 self.write_indent(indent + 1, f"Description: {self.description}")
2632 self.write_indent(indent + 1, f"Optional: {self.optional}")
2633 self.write_indent(indent + 1, f"Type: {self.n_typ.name}")
2635 def __repr__(self):
2636 return "%s<%s>" % (self.__class__.__name__,
2637 self.member_of.fully_qualified_name() + "." +
2638 self.name)
2641class Record_Type(Composite_Type):
2642 """A user-defined record type.
2644 In this example::
2646 type T "optional description of T" extends Root_T {
2647 ^1 ^2 ^3
2649 Note that (1) is part of the :class:`Entity` base, and (2) is part
2650 of the :class:`Composite_Type` base.
2652 :attribute parent: root type or None, indicated by (3) above
2653 :type: Record_Type
2655 :attribute frozen: mapping of frozen components
2656 :type: dict[str, Expression]
2658 :attribute is_final: type is final (i.e. no new components may be declared)
2659 :type: bool
2661 :attribute is_abstract: type is abstract
2662 :type: bool
2664 """
2665 def __init__(self,
2666 name,
2667 description,
2668 location,
2669 package,
2670 n_parent,
2671 is_abstract):
2672 # lobster-exclude: Constructor only declares variables
2673 assert isinstance(n_parent, Record_Type) or n_parent is None
2674 assert isinstance(is_abstract, bool)
2675 super().__init__(name,
2676 description,
2677 location,
2678 package,
2679 n_parent.components if n_parent else None)
2680 self.parent = n_parent
2681 self.frozen = {}
2682 self.is_final = (n_parent.is_final if n_parent else False)
2683 self.is_abstract = is_abstract
2685 def iter_checks(self):
2686 # lobster-trace: LRM.Check_Evaluation_Order
2687 # lobster-trace: LRM.Check_Evaluation_Order_For_Extensions
2688 if self.parent:
2689 yield from self.parent.iter_checks()
2690 yield from self.checks
2692 def dump(self, indent=0): # pragma: no cover
2693 # lobster-exclude: Debugging feature
2694 self.write_indent(indent, f"Record_Type {self.name}")
2695 if self.description:
2696 self.write_indent(indent + 1, f"Description: {self.description}")
2697 if self.parent:
2698 self.write_indent(indent + 1, f"Parent: {self.parent.name}")
2699 self.components.dump(indent + 1, omit_heading=True)
2700 if self.checks:
2701 self.write_indent(indent + 1, "Checks")
2702 for n_check in self.checks:
2703 n_check.dump(indent + 2)
2704 else:
2705 self.write_indent(indent + 1, "Checks: None")
2707 def all_components(self):
2708 """Convenience function to get a list of all components.
2710 :rtype: list[Composite_Component]
2711 """
2712 if self.parent:
2713 return self.parent.all_components() + \
2714 list(self.components.table.values())
2715 else:
2716 return list(self.components.table.values())
2718 def is_subclass_of(self, record_type):
2719 """ Checks if this record type is or inherits from the given type
2721 :param record_type: check if are or extend this type
2722 :type record_type: Record_Type
2724 :returns: true if we are or extend the given type
2725 :rtype: Boolean
2726 """
2727 assert isinstance(record_type, Record_Type)
2729 ptr = self
2730 while ptr:
2731 if ptr is record_type:
2732 return True
2733 else:
2734 ptr = ptr.parent
2735 return False
2737 def is_frozen(self, n_component):
2738 """Test if the given component is frozen.
2740 :param n_component: a composite component of this record type \
2741 (or any of its parents)
2742 :type n_component: Composite_Component
2744 :rtype: bool
2745 """
2746 assert isinstance(n_component, Composite_Component)
2747 if n_component.name in self.frozen:
2748 return True
2749 elif self.parent:
2750 return self.parent.is_frozen(n_component)
2751 else:
2752 return False
2754 def get_freezing_expression(self, n_component):
2755 """Retrieve the frozen value for a frozen component
2757 It is an internal compiler error to call this method with a
2758 component that his not frozen.
2760 :param n_component: a frozen component of this record type \
2761 (or any of its parents)
2762 :type n_component: Composite_Component
2764 :rtype: Expression
2766 """
2767 assert isinstance(n_component, Composite_Component)
2768 if n_component.name in self.frozen: 2768 ↛ 2770line 2768 didn't jump to line 2770 because the condition on line 2768 was always true
2769 return self.frozen[n_component.name]
2770 elif self.parent:
2771 return self.parent.get_freezing_expression(n_component)
2772 else:
2773 assert False
2775 def get_example_value(self):
2776 # lobster-exclude: utility method
2777 return "%s_instance" % self.name
2780class Tuple_Type(Composite_Type):
2781 """A user-defined tuple type.
2783 In this example::
2785 tuple T "optional description of T" {
2786 ^1 ^2
2788 Note that (1) is part of the :class:`Entity` base, and (2) is part
2789 of the :class:`Composite_Type` base.
2791 :attribute separators: list of syntactic separators.
2792 :type: list[Separator]
2794 Note the list of separators will either be empty, or there will be
2795 precisely one less separator than components.
2797 """
2798 def __init__(self, name, description, location, package):
2799 # lobster-trace: LRM.Tuple_Declaration
2800 super().__init__(name,
2801 description,
2802 location,
2803 package)
2804 self.separators = []
2806 def add_separator(self, n_separator):
2807 # lobster-exclude: utility method
2808 assert isinstance(n_separator, Separator)
2809 assert len(self.separators) + 1 == len(self.components.table)
2810 self.separators.append(n_separator)
2812 def iter_separators(self):
2813 """Iterate over all separators"""
2814 # lobster-exclude: utility method
2815 yield from self.separators
2817 def iter_sequence(self):
2818 """Iterate over all components and separators in syntactic order"""
2819 # lobster-exclude: utility method
2820 if self.separators:
2821 for i, n_component in enumerate(self.components.table.values()):
2822 yield n_component
2823 if i < len(self.separators):
2824 yield self.separators[i]
2825 else:
2826 yield from self.components.table.values()
2828 def has_separators(self):
2829 """Returns true if a tuple type requires separators"""
2830 # lobster-exclude: utility method
2831 return bool(self.separators)
2833 def dump(self, indent=0): # pragma: no cover
2834 # lobster-exclude: Debugging feature
2835 self.write_indent(indent, f"Tuple_Type {self.name}")
2836 if self.description:
2837 self.write_indent(indent + 1, f"Description: {self.description}")
2838 self.write_indent(indent + 1, "Fields")
2839 for n_item in self.iter_sequence():
2840 n_item.dump(indent + 2)
2841 if self.checks:
2842 self.write_indent(indent + 1, "Checks")
2843 for n_check in self.checks:
2844 n_check.dump(indent + 2)
2845 else:
2846 self.write_indent(indent + 1, "Checks: None")
2848 def perform_type_checks(self, mh, value, gstab):
2849 # lobster-trace: LRM.Check_Evaluation_Order
2850 assert isinstance(mh, Message_Handler)
2851 assert isinstance(gstab, Symbol_Table)
2853 if isinstance(value, Tuple_Aggregate): 2853 ↛ 2860line 2853 didn't jump to line 2860 because the condition on line 2853 was always true
2854 ok = True
2855 for check in self.iter_checks():
2856 if not check.perform(mh, value, gstab): 2856 ↛ 2857line 2856 didn't jump to line 2857 because the condition on line 2856 was never true
2857 ok = False
2858 return ok
2859 else:
2860 assert isinstance(value, Implicit_Null)
2861 return True
2863 def get_example_value(self):
2864 # lobster-exclude: utility method
2865 parts = []
2866 for n_item in self.iter_sequence():
2867 if isinstance(n_item, Composite_Component):
2868 parts.append(n_item.n_typ.get_example_value())
2869 else:
2870 parts.append(n_item.to_string())
2871 if self.has_separators():
2872 return " ".join(parts)
2873 else:
2874 return "(%s)" % ", ".join(parts)
2877class Separator(Node):
2878 # lobster-trace: LRM.Tuple_Declaration
2879 """User-defined syntactic separator
2881 For example::
2883 separator x
2884 ^1
2886 :attribute token: token used to separate fields of the tuple
2887 :type: Token
2888 """
2889 def __init__(self, token):
2890 super().__init__(token.location)
2891 assert isinstance(token, Token) and token.kind in ("IDENTIFIER",
2892 "AT",
2893 "COLON",
2894 "SEMICOLON")
2895 self.token = token
2897 def to_string(self):
2898 return {
2899 "AT" : "@",
2900 "COLON" : ":",
2901 "SEMICOLON" : ";"
2902 }.get(self.token.kind, self.token.value)
2904 def dump(self, indent=0): # pragma: no cover
2905 self.write_indent(indent, f"Separator {self.token.value}")
2908class Enumeration_Type(Concrete_Type):
2909 """User-defined enumeration types.
2911 For example::
2913 enum T "potato" {
2914 ^1 ^2
2916 :attribute description: user supplied optional description, or None
2917 :type: str
2919 :attribute literals: the literals in this enumeration
2920 :type: Symbol_Table[Enumeration_Literal_Spec]
2922 """
2923 def __init__(self, name, description, location, package):
2924 # lobster-trace: LRM.Described_Name_Description
2925 super().__init__(name, location, package)
2926 assert isinstance(description, str) or description is None
2927 self.literals = Symbol_Table()
2928 self.description = description
2930 def dump(self, indent=0): # pragma: no cover
2931 # lobster-exclude: Debugging feature
2932 self.write_indent(indent, f"Enumeration_Type {self.name}")
2933 if self.description:
2934 self.write_indent(indent + 1, f"Description: {self.description}")
2935 self.literals.dump(indent + 1, omit_heading=True)
2937 def get_example_value(self):
2938 # lobster-exclude: utility method
2939 options = list(self.literals.values())
2940 if options:
2941 choice = len(options) // 2
2942 return self.name + "." + choice.name
2943 else:
2944 return "ERROR"
2947class Enumeration_Literal_Spec(Typed_Entity):
2948 """Declared literal in an enumeration declaration.
2950 Note that for literals mentioned later in record object
2951 declarations, we use :class:`Enumeration_Literal`. Literal specs
2952 are used here::
2954 enum ASIL {
2955 QM "not safety related"
2956 ^1 ^2
2958 :attribute description: the optional user-supplied description, or None
2959 :type: str
2961 """
2962 def __init__(self, name, description, location, enum):
2963 # lobster-trace: LRM.Described_Name_Description
2964 super().__init__(name, location, enum)
2965 assert isinstance(description, str) or description is None
2966 assert isinstance(enum, Enumeration_Type)
2967 self.description = description
2969 def dump(self, indent=0): # pragma: no cover
2970 # lobster-exclude: Debugging feature
2971 self.write_indent(indent, f"Enumeration_Literal_Spec {self.name}")
2972 if self.description:
2973 self.write_indent(indent + 1, f"Description: {self.description}")
2976class Record_Object(Typed_Entity):
2977 """A declared instance of a record type.
2979 This is going to be the bulk of all entities created by TRLC::
2981 section "Potato" {
2982 ^5
2983 Requirement PotatoReq {
2984 ^1 ^2
2985 component1 = 42
2986 ^3 ^4
2988 Note that the name (see 2) and type (see 1) of the object is
2989 provided by the name attribute of the :class:`Typed_Entity` base
2990 class.
2992 :attribute field: the specific values for all components (see 3 and 4)
2993 :type: dict[str, Expression]
2995 :attribute section: None or the section this record is contained in (see 5)
2996 :type: Section
2998 :attribute n_package: The package in which this record is declared in
2999 :type: Section
3001 The actual type of expressions in the field attribute are limited
3002 to:
3004 * :class:`Literal`
3005 * :class:`Unary_Expression`
3006 * :class:`Array_Aggregate`
3007 * :class:`Tuple_Aggregate`
3008 * :class:`Record_Reference`
3009 * :class:`Implicit_Null`
3011 """
3012 def __init__(self, name, location, n_typ, section, n_package):
3013 # lobster-trace: LRM.Section_Declaration
3014 # lobster-trace: LRM.Unspecified_Optional_Components
3015 # lobster-trace: LRM.Record_Object_Declaration
3017 assert isinstance(n_typ, Record_Type)
3018 assert isinstance(section, list) or section is None
3019 assert isinstance(n_package, Package)
3020 super().__init__(name, location, n_typ)
3021 self.field = {
3022 comp.name: Implicit_Null(self, comp)
3023 for comp in self.n_typ.all_components()
3024 }
3025 self.section = section
3026 self.n_package = n_package
3028 def fully_qualified_name(self):
3029 """Return the FQN for this type (i.e. PACKAGE.NAME)
3031 :returns: the object's full name
3032 :rtype: str
3033 """
3034 return self.n_package.name + "." + self.name
3036 def to_python_dict(self):
3037 """Return an evaluated and simplified object for Python.
3039 For example it might provide::
3041 {"foo" : [1, 2, 3],
3042 "bar" : None,
3043 "baz" : "value"}
3045 This is a function especially designed for the Python API. The
3046 name of the object itself is not in this returned dictionary.
3048 """
3049 return {name: value.to_python_object()
3050 for name, value in self.field.items()}
3052 def is_component_implicit_null(self, component) -> bool:
3053 return not isinstance(self.field[component.name], Implicit_Null)
3055 def assign(self, component, value):
3056 assert isinstance(component, Composite_Component)
3057 assert isinstance(value, (Literal,
3058 Array_Aggregate,
3059 Tuple_Aggregate,
3060 Record_Reference,
3061 Implicit_Null,
3062 Unary_Expression)), \
3063 "value is %s" % value.__class__.__name__
3064 if self.is_component_implicit_null(component): 3064 ↛ 3065line 3064 didn't jump to line 3065 because the condition on line 3064 was never true
3065 raise KeyError(f"Component {component.name} already \
3066 assigned to {self.n_typ.name} {self.name}!")
3067 self.field[component.name] = value
3069 def dump(self, indent=0): # pragma: no cover
3070 # lobster-exclude: Debugging feature
3071 self.write_indent(indent, f"Record_Object {self.name}")
3072 self.write_indent(indent + 1, f"Type: {self.n_typ.name}")
3073 for key, value in self.field.items():
3074 self.write_indent(indent + 1, f"Field {key}")
3075 value.dump(indent + 2)
3076 if self.section:
3077 self.section[-1].dump(indent + 1)
3079 def resolve_references(self, mh):
3080 assert isinstance(mh, Message_Handler)
3081 for val in self.field.values():
3082 val.resolve_references(mh)
3084 def perform_checks(self, mh, gstab):
3085 # lobster-trace: LRM.Check_Evaluation_Order
3086 # lobster-trace: LRM.Evaluation_Of_Checks
3087 assert isinstance(mh, Message_Handler)
3088 assert isinstance(gstab, Symbol_Table)
3090 ok = True
3092 # First evaluate all tuple checks
3093 for n_comp in self.n_typ.all_components():
3094 if not n_comp.n_typ.perform_type_checks(mh, 3094 ↛ 3097line 3094 didn't jump to line 3097 because the condition on line 3094 was never true
3095 self.field[n_comp.name],
3096 gstab):
3097 ok = False
3099 # TODO: Is there a bug here (a check relies on a tuple check)?
3101 # Then evaluate all record checks
3102 for check in self.n_typ.iter_checks():
3103 # Prints messages, if applicable. Raises exception on
3104 # fatal checks, which causes this to abort.
3105 if not check.perform(mh, self, gstab):
3106 ok = False
3108 return ok
3110 def __repr__(self):
3111 return "%s<%s>" % (self.__class__.__name__,
3112 self.n_package.name + "." +
3113 self.n_typ.name + "." +
3114 self.name)
3117class Section(Entity):
3118 # lobster-trace: LRM.Section_Declaration
3119 """A section for readability
3121 This represents a section construct in TRLC files to group record
3122 objects together::
3124 section "Foo" {
3125 ^^^^^ parent section
3126 section "Bar" {
3127 ^^^^^ section
3129 :attribute parent: the parent section or None
3130 :type: Section
3132 """
3133 def __init__(self, name, location, parent):
3134 super().__init__(name, location)
3135 assert isinstance(parent, Section) or parent is None
3136 self.parent = parent
3138 def dump(self, indent=0): # pragma: no cover
3139 self.write_indent(indent, f"Section {self.name}")
3140 if self.parent is None:
3141 self.write_indent(indent + 1, "Parent: None")
3142 else:
3143 self.write_indent(indent + 1, f"Parent: {self.parent.name}")
3146##############################################################################
3147# Symbol Table & Scopes
3148##############################################################################
3150class Symbol_Table:
3151 """ Symbol table mapping names to entities
3152 """
3153 def __init__(self, parent=None):
3154 # lobster-exclude: Constructor only declares variables
3155 assert isinstance(parent, Symbol_Table) or parent is None
3156 self.parent = parent
3157 self.imported = []
3158 self.table = OrderedDict()
3159 self.trlc_files = []
3160 self.section_names = []
3162 @staticmethod
3163 def simplified_name(name):
3164 # lobster-trace: LRM.Sufficiently_Distinct
3165 assert isinstance(name, str)
3166 return name.lower().replace("_", "")
3168 def all_names(self):
3169 # lobster-exclude: API for users
3170 """ All names in the symbol table
3172 :rtype: set[str]
3173 """
3174 rv = set(item.name for item in self.table.values())
3175 if self.parent:
3176 rv |= self.parent.all_names()
3177 return rv
3179 def iter_record_objects_by_section(self):
3180 """API for users
3182 Retriving information about the section hierarchy for record objects
3183 Inputs: folder with trlc files where trlc files have sections,
3184 sub sections and record objects
3185 Output: Information about sections and level of sections,
3186 record objects and levels of record object
3187 """
3188 for record_object in self.iter_record_objects():
3189 location = record_object.location.file_name
3190 if location not in self.trlc_files: 3190 ↛ 3193line 3190 didn't jump to line 3193 because the condition on line 3190 was always true
3191 self.trlc_files.append(location)
3192 yield location
3193 if record_object.section:
3194 object_level = len(record_object.section) - 1
3195 for level, section in enumerate(record_object.section):
3196 if section not in self.section_names: 3196 ↛ 3195line 3196 didn't jump to line 3195 because the condition on line 3196 was always true
3197 self.section_names.append(section)
3198 yield section.name, level
3199 yield record_object, object_level
3200 else:
3201 object_level = 0
3202 yield record_object, object_level
3204 def iter_record_objects(self):
3205 # lobster-exclude: API for users
3206 """ Iterate over all record objects
3208 :rtype: iterable[Record_Object]
3209 """
3210 for item in self.table.values():
3211 if isinstance(item, Package):
3212 yield from item.symbols.iter_record_objects()
3214 elif isinstance(item, Record_Object):
3215 yield item
3217 def values(self, subtype=None):
3218 # lobster-exclude: API for users
3219 assert subtype is None or isinstance(subtype, type)
3220 if self.parent:
3221 yield from self.parent.values(subtype)
3222 for name in sorted(self.table):
3223 if subtype is None or isinstance(self.table[name], subtype):
3224 yield self.table[name]
3226 def make_visible(self, stab):
3227 assert isinstance(stab, Symbol_Table)
3228 self.imported.append(stab)
3230 def register(self, mh, entity):
3231 # lobster-trace: LRM.Duplicate_Types
3232 # lobster-trace: LRM.Unique_Enumeration_Literals
3233 # lobster-trace: LRM.Tuple_Unique_Field_Names
3234 # lobster-trace: LRM.Sufficiently_Distinct
3235 # lobster-trace: LRM.Unique_Object_Names
3237 assert isinstance(mh, Message_Handler)
3238 assert isinstance(entity, Entity)
3240 simple_name = self.simplified_name(entity.name)
3242 if self.contains_raw(simple_name):
3243 pdef = self.lookup_direct(mh, entity.name, entity.location,
3244 simplified=True)
3245 if pdef.name == entity.name:
3246 mh.error(entity.location,
3247 "duplicate definition, previous definition at %s" %
3248 mh.cross_file_reference(pdef.location))
3249 else:
3250 mh.error(entity.location,
3251 "%s is too similar to %s, declared at %s" %
3252 (entity.name,
3253 pdef.name,
3254 mh.cross_file_reference(pdef.location)))
3256 else:
3257 self.table[simple_name] = entity
3259 def __contains__(self, name):
3260 # lobster-trace: LRM.Described_Name_Equality
3261 return self.contains(name)
3263 def contains_raw(self, simple_name, precise_name=None):
3264 # lobster-trace: LRM.Described_Name_Equality
3265 # lobster-trace: LRM.Sufficiently_Distinct
3266 #
3267 # Internal function to test if the simplified name is in the
3268 # table.
3269 assert isinstance(simple_name, str)
3270 assert isinstance(precise_name, str) or precise_name is None
3272 if simple_name in self.table:
3273 # No need to continue searching since registering a
3274 # clashing name would have been stopped
3275 return precise_name is None or \
3276 self.table[simple_name].name == precise_name
3278 elif self.parent:
3279 return self.parent.contains_raw(simple_name, precise_name)
3281 for stab in self.imported:
3282 if stab.contains_raw(simple_name, precise_name):
3283 return True
3285 return False
3287 def contains(self, name):
3288 # lobster-trace: LRM.Described_Name_Equality
3289 """ Tests if the given name is in the table
3291 :param name: the name to test
3292 :type name: str
3294 :rtype: bool
3295 """
3296 assert isinstance(name, str)
3297 return self.contains_raw(self.simplified_name(name), name)
3299 def lookup_assuming(self, mh, name, required_subclass=None):
3300 # lobster-trace: LRM.Described_Name_Equality
3301 # lobster-trace: LRM.Sufficiently_Distinct
3302 """Retrieve an object from the table assuming its there
3304 This is intended for the API specifically where you want to
3305 e.g. find some used-defined types you know are there.
3307 :param mh: The message handler to use
3308 :type mh: Message_Handler
3310 :param name: The name to search for
3311 :type name: str
3313 :param required_subclass: If set, creates an error if the object \
3314 is not an instance of the given class
3315 :type required_subclass: type
3317 :raise TRLC_Error: if the object is not of the required subclass
3318 :returns: the specified entity (or None if it does not exist)
3319 :rtype: Entity
3321 """
3322 assert isinstance(mh, Message_Handler)
3323 assert isinstance(name, str)
3324 assert isinstance(required_subclass, type) or required_subclass is None
3326 simple_name = self.simplified_name(name)
3328 ptr = self
3329 for ptr in [self] + self.imported: 3329 ↛ 3347line 3329 didn't jump to line 3347 because the loop on line 3329 didn't complete
3330 while ptr: 3330 ↛ 3329line 3330 didn't jump to line 3329 because the condition on line 3330 was always true
3331 if simple_name in ptr.table: 3331 ↛ 3345line 3331 didn't jump to line 3345 because the condition on line 3331 was always true
3332 rv = ptr.table[simple_name]
3333 if rv.name != name: 3333 ↛ 3334line 3333 didn't jump to line 3334 because the condition on line 3333 was never true
3334 return None
3336 if required_subclass is not None and \ 3336 ↛ 3338line 3336 didn't jump to line 3338 because the condition on line 3336 was never true
3337 not isinstance(rv, required_subclass):
3338 mh.error(rv.location,
3339 "%s %s is not a %s" %
3340 (rv.__class__.__name__,
3341 name,
3342 required_subclass.__name__))
3343 return rv
3344 else:
3345 ptr = ptr.parent
3347 return None
3349 def lookup_direct(self,
3350 mh,
3351 name,
3352 error_location,
3353 required_subclass=None,
3354 simplified=False):
3355 # lobster-trace: LRM.Described_Name_Equality
3356 # lobster-trace: LRM.Sufficiently_Distinct
3357 # lobster-trace: LRM.Valid_Base_Names
3358 # lobster-trace: LRM.Valid_Access_Prefixes
3359 # lobster-trace: LRM.Valid_Function_Prefixes
3360 """Retrieve an object from the table
3362 For example::
3364 pkg = stab.lookup_direct(mh,
3365 "potato",
3366 Location("foobar.txt", 42),
3367 Package)
3369 This would search for an object named ``potato``. If it is
3370 found, and it is a package, it is returned. If it is not a
3371 Package, then the following error is issued::
3373 foobar.txt:42: error: Enumeration_Type potato is not a Package
3375 If it is not found at all, then the following error is issued::
3377 foobar.txt:42: error: unknown symbol potato
3379 :param mh: The message handler to use
3380 :type mh: Message_Handler
3382 :param name: The name to search for
3383 :type name: str
3385 :param error_location: Where to create the error if the name is \
3386 not found
3387 :type error_location: Location
3389 :param required_subclass: If set, creates an error if the object \
3390 is not an instance of the given class
3391 :type required_subclass: type
3393 :param simplified: If set, look up the given simplified name instead \
3394 of the actual name
3395 :type simplified: bool
3397 :raise TRLC_Error: if the name is not in the table
3398 :raise TRLC_Error: if the object is not of the required subclass
3399 :returns: the specified entity
3400 :rtype: Entity
3402 """
3403 assert isinstance(mh, Message_Handler)
3404 assert isinstance(name, str)
3405 assert isinstance(error_location, Location)
3406 assert isinstance(required_subclass, type) or required_subclass is None
3407 assert isinstance(simplified, bool)
3409 simple_name = self.simplified_name(name)
3410 ptr = self
3411 options = []
3413 for ptr in [self] + self.imported:
3414 while ptr:
3415 if simple_name in ptr.table:
3416 rv = ptr.table[simple_name]
3417 if not simplified and rv.name != name: 3417 ↛ 3418line 3417 didn't jump to line 3418 because the condition on line 3417 was never true
3418 mh.error(error_location,
3419 "unknown symbol %s, did you mean %s?" %
3420 (name,
3421 rv.name))
3423 if required_subclass is not None and \
3424 not isinstance(rv, required_subclass):
3425 mh.error(error_location,
3426 "%s %s is not a %s" %
3427 (rv.__class__.__name__,
3428 name,
3429 required_subclass.__name__))
3430 return rv
3431 else:
3432 options += list(item.name
3433 for item in ptr.table.values())
3434 ptr = ptr.parent
3436 matches = get_close_matches(
3437 word = name,
3438 possibilities = options,
3439 n = 1)
3441 if matches:
3442 mh.error(error_location,
3443 "unknown symbol %s, did you mean %s?" %
3444 (name,
3445 matches[0]))
3446 else:
3447 mh.error(error_location,
3448 "unknown symbol %s" % name)
3450 def lookup(self, mh, referencing_token, required_subclass=None):
3451 # lobster-trace: LRM.Described_Name_Equality
3452 assert isinstance(mh, Message_Handler)
3453 assert isinstance(referencing_token, Token)
3454 assert referencing_token.kind in ("IDENTIFIER", "BUILTIN")
3455 assert isinstance(required_subclass, type) or required_subclass is None
3457 return self.lookup_direct(
3458 mh = mh,
3459 name = referencing_token.value,
3460 error_location = referencing_token.location,
3461 required_subclass = required_subclass)
3463 def write_indent(self, indent, message): # pragma: no cover
3464 # lobster-exclude: Debugging feature
3465 assert isinstance(indent, int)
3466 assert indent >= 0
3467 assert isinstance(message, str)
3468 print(" " * (3 * indent) + message)
3470 def dump(self, indent=0, omit_heading=False): # pragma: no cover
3471 # lobster-exclude: Debugging feature
3472 if omit_heading:
3473 new_indent = indent
3474 else:
3475 self.write_indent(indent, "Symbol_Table")
3476 new_indent = indent + 1
3477 ptr = self
3478 while ptr:
3479 for name in ptr.table:
3480 ptr.table[name].dump(new_indent)
3481 ptr = ptr.parent
3483 @classmethod
3484 def create_global_table(cls, mh):
3485 # lobster-trace: LRM.Builtin_Types
3486 # lobster-trace: LRM.Builtin_Functions
3487 # lobster-trace: LRM.Builtin_Type_Conversion_Functions
3488 # lobster-trace: LRM.Signature_Len
3489 # lobster-trace: LRM.Signature_String_End_Functions
3490 # lobster-trace: LRM.Signature_Matches
3492 stab = Symbol_Table()
3493 stab.register(mh, Builtin_Integer())
3494 stab.register(mh, Builtin_Decimal())
3495 stab.register(mh, Builtin_Boolean())
3496 stab.register(mh, Builtin_String())
3497 stab.register(mh, Builtin_Markup_String())
3498 stab.register(mh,
3499 Builtin_Function("len", 1))
3500 stab.register(mh,
3501 Builtin_Function("startswith", 2))
3502 stab.register(mh,
3503 Builtin_Function("endswith", 2))
3504 stab.register(mh,
3505 Builtin_Function("matches", 2))
3506 stab.register(mh,
3507 Builtin_Function("oneof", 1, arity_at_least=True))
3509 return stab
3512class Scope:
3513 def __init__(self):
3514 # lobster-exclude: Constructor only declares variables
3515 self.scope = []
3517 def push(self, stab):
3518 assert isinstance(stab, Symbol_Table)
3519 self.scope.append(stab)
3521 def pop(self):
3522 self.scope.pop()
3524 def contains(self, name):
3525 assert isinstance(name, str)
3527 for stab in reversed(self.scope):
3528 if stab.contains(name):
3529 return True
3530 return False
3532 def lookup(self, mh, referencing_token, required_subclass=None):
3533 assert len(self.scope) >= 1
3534 assert isinstance(mh, Message_Handler)
3535 assert isinstance(referencing_token, Token)
3536 assert referencing_token.kind in ("IDENTIFIER", "BUILTIN")
3537 assert isinstance(required_subclass, type) or required_subclass is None
3539 for stab in reversed(self.scope[1:]):
3540 if stab.contains(referencing_token.value):
3541 return stab.lookup(mh, referencing_token, required_subclass)
3542 return self.scope[0].lookup(mh, referencing_token, required_subclass)
3544 def size(self):
3545 return len(self.scope)