Coverage for trlc/ast.py: 90%
1199 statements
« prev ^ index » next coverage.py v7.7.1, created at 2025-03-27 00:52 +0000
« prev ^ index » next coverage.py v7.7.1, created at 2025-03-27 00:52 +0000
1#!/usr/bin/env python3
2#
3# TRLC - Treat Requirements Like Code
4# Copyright (C) 2022-2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
5# Copyright (C) 2024 Florian Schanda
6#
7# This file is part of the TRLC Python Reference Implementation.
8#
9# TRLC is free software: you can redistribute it and/or modify it
10# under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# TRLC is distributed in the hope that it will be useful, but WITHOUT
15# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
17# License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with TRLC. If not, see <https://www.gnu.org/licenses/>.
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, "dump not implemented for %s" % 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, "Type: %s" % 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, "Compilation_Unit (%s)" %
231 self.location.file_name)
232 for t_import in self.raw_imports:
233 self.write_indent(indent + 1, "Import: %s" % t_import.value)
234 for n_item in self.items:
235 n_item.dump(indent + 1)
237 def set_package(self, pkg):
238 # lobster-trace: LRM.Current_Package
239 assert isinstance(pkg, Package)
240 self.package = pkg
242 def add_import(self, mh, t_import):
243 # lobster-trace: LRM.Import_Visibility
244 # lobster-trace: LRM.Self_Imports
245 assert isinstance(mh, Message_Handler)
246 assert isinstance(t_import, Token)
247 assert t_import.kind == "IDENTIFIER"
249 if t_import.value == self.package.name:
250 mh.error(t_import.location,
251 "package %s cannot import itself" % self.package.name)
253 # Skip duplicates
254 for t_previous in self.raw_imports:
255 if t_previous.value == t_import.value:
256 mh.warning(t_import.location,
257 "duplicate import of package %s" % t_import.value)
258 return
260 self.raw_imports.append(t_import)
262 def resolve_imports(self, mh, stab):
263 # lobster-trace: LRM.Import_Visibility
264 assert isinstance(mh, Message_Handler)
265 assert isinstance(stab, Symbol_Table)
266 self.imports = set()
267 for t_import in self.raw_imports:
268 # We can ignore errors here, because that just means we
269 # generate more error later.
270 try:
271 a_import = stab.lookup(mh, t_import, Package)
272 self.imports.add(a_import)
273 a_import.set_ast_link(t_import)
274 except TRLC_Error:
275 pass
277 def is_visible(self, n_pkg):
278 # lobster-trace: LRM.Import_Visibility
279 assert self.imports is not None
280 assert isinstance(n_pkg, Package)
281 return n_pkg == self.package or n_pkg in self.imports
283 def add_item(self, node):
284 # lobster-trace: LRM.RSL_File
285 # lobster-trace: LRM.TRLC_File
286 assert isinstance(node, (Concrete_Type,
287 Check_Block,
288 Record_Object)), \
289 "trying to add %s to a compilation unit" % node.__class__.__name__
290 self.items.append(node)
293class Check(Node):
294 """User defined check
296 This represent a single user-defined check inside a check block::
298 checks T {
299 a /= null implies a > 5, warning "potato", a
300 ^^^^^^^^^^^^^^^^^^^^^^^1 ^2 ^3 ^4
302 :attribute n_type: The tuple/record type this check applies to
303 :type: Composite_Type
305 :attribute n_expr: The boolean expression for the check (see 1)
306 :type: Expression
308 :attribute n_anchor: The (optional) record component where the message \
309 should be issued (or None) (see 4)
310 :type: Composite_Component
312 :attribute severity: warning, error, or fatal (see 2; also if this is \
313 not specified the default is 'error')
314 :type: str
316 :attribute message: the user-supplied message (see 3)
317 :type: str
318 """
319 def __init__(self,
320 n_type,
321 n_expr,
322 n_anchor,
323 severity,
324 t_message,
325 extrainfo):
326 # lobster-trace: LRM.Check_Block
327 assert isinstance(n_type, Composite_Type)
328 assert isinstance(n_expr, Expression)
329 assert isinstance(n_anchor, Composite_Component) or n_anchor is None
330 assert severity in ("warning", "error", "fatal")
331 assert isinstance(t_message, Token)
332 assert t_message.kind == "STRING"
333 assert isinstance(extrainfo, str) or extrainfo is None
334 super().__init__(t_message.location)
336 self.n_type = n_type
337 self.n_expr = n_expr
338 self.n_anchor = n_anchor
339 self.severity = severity
340 # lobster-trace: LRM.No_Newlines_In_Message
341 # This is the error recovery strategy if we find newlines in
342 # the short error messages: we just remove them. The error
343 # raised is non-fatal.
344 self.message = t_message.value.replace("\n", " ")
345 self.extrainfo = extrainfo
347 def dump(self, indent=0): # pragma: no cover
348 # lobster-exclude: Debugging feature
349 if self.severity == "warning":
350 self.write_indent(indent, "Warning '%s'" % self.message)
351 elif self.severity == "error":
352 self.write_indent(indent, "Error '%s'" % self.message)
353 else:
354 self.write_indent(indent, "Fatal error '%s'" % self.message)
355 if self.n_anchor:
356 self.write_indent(indent + 1, "Anchor: %s" % self.n_anchor.name)
357 self.n_expr.dump(indent + 1)
359 def get_real_location(self, composite_object):
360 # lobster-exclude: LRM.Anchoring
361 assert isinstance(composite_object, (Record_Object,
362 Tuple_Aggregate))
363 if isinstance(composite_object, Record_Object):
364 fields = composite_object.field
365 else:
366 fields = composite_object.value
368 if self.n_anchor is None or fields[self.n_anchor.name] is None:
369 return composite_object.location
370 else:
371 return fields[self.n_anchor.name].location
373 def perform(self, mh, composite_object):
374 # lobster-trace: LRM.Check_Messages
375 # lobster-trace: LRM.Check_Severity
376 assert isinstance(mh, Message_Handler)
377 assert isinstance(composite_object, (Record_Object,
378 Tuple_Aggregate))
380 if isinstance(composite_object, Record_Object):
381 result = self.n_expr.evaluate(mh, copy(composite_object.field))
382 else:
383 result = self.n_expr.evaluate(mh, copy(composite_object.value))
384 assert isinstance(result.value, bool)
386 if not result.value:
387 loc = self.get_real_location(composite_object)
388 if self.severity == "warning":
389 mh.warning(location = loc,
390 message = self.message,
391 explanation = self.extrainfo,
392 user = True)
393 else:
394 mh.error(location = loc,
395 message = self.message,
396 explanation = self.extrainfo,
397 fatal = self.severity == "fatal",
398 user = True)
399 return False
401 return True
403##############################################################################
404# AST Nodes (Expressions)
405##############################################################################
408class Unary_Operator(Enum):
409 # lobster-exclude: Utility enumeration for unary operators
410 MINUS = auto()
411 PLUS = auto()
412 LOGICAL_NOT = auto()
413 ABSOLUTE_VALUE = auto()
415 STRING_LENGTH = auto()
416 ARRAY_LENGTH = auto()
418 CONVERSION_TO_INT = auto()
419 CONVERSION_TO_DECIMAL = auto()
422class Binary_Operator(Enum):
423 # lobster-exclude: Utility enumeration for binary operators
424 LOGICAL_AND = auto() # Short-circuit
425 LOGICAL_OR = auto() # Short-circuit
426 LOGICAL_XOR = auto()
427 LOGICAL_IMPLIES = auto() # Short-circuit
429 COMP_EQ = auto()
430 COMP_NEQ = auto()
431 COMP_LT = auto()
432 COMP_LEQ = auto()
433 COMP_GT = auto()
434 COMP_GEQ = auto()
436 STRING_CONTAINS = auto()
437 STRING_STARTSWITH = auto()
438 STRING_ENDSWITH = auto()
439 STRING_REGEX = auto()
441 ARRAY_CONTAINS = auto()
443 PLUS = auto()
444 MINUS = auto()
445 TIMES = auto()
446 DIVIDE = auto()
447 REMAINDER = auto()
449 POWER = auto()
451 INDEX = auto()
454class Expression(Node, metaclass=ABCMeta):
455 """Abstract base class for all expressions.
457 :attribute typ: The type of this expression (or None for null values)
458 :type: Type
459 """
460 def __init__(self, location, typ):
461 # lobster-exclude: Constructor only declares variables
462 super().__init__(location)
463 assert typ is None or isinstance(typ, Type)
464 self.typ = typ
466 def evaluate(self, mh, context): # pragma: no cover
467 """Evaluate the expression in the given context
469 The context can be None, in which case the expression is
470 evaluated in a static context. Otherwise it must be a
471 dictionary that maps names (such as record fields or
472 quantified variables) to expressions.
474 :param mh: the message handler to use
475 :type mh: Message_Handler
476 :param context: name mapping or None (for a static context)
477 :type context: dict[str, Expression]
478 :raise TRLC_Error: if the expression cannot be evaluated
479 :return: result of the evaluation
480 :rtype: Value
481 """
482 assert isinstance(mh, Message_Handler)
483 assert context is None or isinstance(context, dict)
484 assert False, "evaluate not implemented for %s" % \
485 self.__class__.__name__
487 @abstractmethod
488 def to_string(self): # pragma: no cover
489 assert False, "to_string not implemented for %s" % \
490 self.__class__.__name__
492 def ensure_type(self, mh, typ):
493 # lobster-trace: LRM.Restricted_Null
494 # lobster-trace: LRM.Null_Is_Invalid
496 assert isinstance(typ, (type, Type))
497 if self.typ is None:
498 mh.error(self.location,
499 "null is not permitted here")
500 elif isinstance(typ, type) and not isinstance(self.typ, typ):
501 mh.error(self.location,
502 "expected expression of type %s, got %s instead" %
503 (typ.__name__,
504 self.typ.__class__.__name__))
505 elif isinstance(typ, Type) and self.typ != typ:
506 mh.error(self.location,
507 "expected expression of type %s, got %s instead" %
508 (typ.name,
509 self.typ.name))
511 def resolve_references(self, mh):
512 assert isinstance(mh, Message_Handler)
514 @abstractmethod
515 def can_be_null(self):
516 """Test if the expression could return null
518 Checks the expression if it could generate a null value
519 *without* raising an error. For example `x` could generate a
520 null value if `x` is a record component that is
521 optional. However `x + 1` could not, since an error would
522 occur earlier.
524 :return: possibility of encountering null
525 :rtype: bool
527 """
528 assert False, "can_be_null not implemented for %s" % \
529 self.__class__.__name__
532class Implicit_Null(Expression):
533 """Synthesised null values
535 When a record object or tuple aggregate is declared and an
536 optional component or field is not specified, we synthesise an
537 implicit null expression for this.
539 For example given this TRLC type::
541 type T {
542 x optional Integer
543 }
545 And this declaration::
547 T Potato {}
549 Then the field mapping for Potato will be::
551 {x: Implicit_Null}
553 Each field will get its own implicit null. Further note that this
554 implicit null is distinct from the explicit :class:`Null_Literal`
555 that can appear in check expressions.
557 """
558 def __init__(self, composite_object, composite_component):
559 # lobster-trace: LRM.Unspecified_Optional_Components
560 assert isinstance(composite_object, (Record_Object,
561 Tuple_Aggregate))
562 assert isinstance(composite_component, Composite_Component)
563 super().__init__(composite_object.location, None)
565 def to_string(self):
566 return "null"
568 def evaluate(self, mh, context):
569 # lobster-trace: LRM.Unspecified_Optional_Components
570 assert isinstance(mh, Message_Handler)
571 assert context is None or isinstance(context, dict)
572 return Value(self.location, None, None)
574 def to_python_object(self):
575 return None
577 def dump(self, indent=0): # pragma: no cover
578 # lobster-exclude: Debugging feature
579 self.write_indent(indent, "Implicit_Null")
581 def can_be_null(self):
582 return True
585class Literal(Expression, metaclass=ABCMeta):
586 """Abstract base for all Literals
588 Does not offer any additional features, but it's a nice way to
589 group together all literal types. This is useful if you want to
590 check if you are dealing with a literal::
592 isinstance(my_expression, Literal)
594 """
595 @abstractmethod
596 def to_python_object(self):
597 assert False
600class Null_Literal(Literal):
601 # lobster-trace: LRM.Primary
602 """The null literal
604 This can appear in check expressions::
606 a /= null implies a > 5
607 ^^^^
609 Please note that this is distinct from the :class:`Implicit_Null`
610 values that appear in record objects.
612 """
613 def __init__(self, token):
614 assert isinstance(token, Token)
615 assert token.kind == "KEYWORD"
616 assert token.value == "null"
617 super().__init__(token.location, None)
619 def dump(self, indent=0): # pragma: no cover
620 self.write_indent(indent, "Null Literal")
622 def to_string(self):
623 return "null"
625 def evaluate(self, mh, context):
626 assert isinstance(mh, Message_Handler)
627 assert context is None or isinstance(context, dict)
628 return Value(self.location, None, None)
630 def to_python_object(self):
631 return None
633 def can_be_null(self):
634 return True
637class Integer_Literal(Literal):
638 # lobster-trace: LRM.Integer_Values
639 # lobster-trace: LRM.Primary
640 """Integer literals
642 Note that these are always positive. A negative integer is
643 actually a unary negation expression, operating on a positive
644 integer literal::
646 x == -5
648 This would create the following tree::
650 OP_EQUALITY
651 NAME_REFERENCE x
652 UNARY_EXPRESSION -
653 INTEGER_LITERAL 5
655 :attribute value: the non-negative integer value
656 :type: int
657 """
658 def __init__(self, token, typ):
659 assert isinstance(token, Token)
660 assert token.kind == "INTEGER"
661 assert isinstance(typ, Builtin_Integer)
662 super().__init__(token.location, typ)
664 self.value = token.value
666 def dump(self, indent=0): # pragma: no cover
667 self.write_indent(indent, "Integer Literal %u" % self.value)
669 def to_string(self):
670 return str(self.value)
672 def evaluate(self, mh, context):
673 assert isinstance(mh, Message_Handler)
674 assert context is None or isinstance(context, dict)
675 return Value(self.location, self.value, self.typ)
677 def to_python_object(self):
678 return self.value
680 def can_be_null(self):
681 return False
684class Decimal_Literal(Literal):
685 # lobster-trace: LRM.Decimal_Values
686 # lobster-trace: LRM.Primary
687 """Decimal literals
689 Note that these are always positive. A negative decimal is
690 actually a unary negation expression, operating on a positive
691 decimal literal::
693 x == -5.0
695 This would create the following tree::
697 OP_EQUALITY
698 NAME_REFERENCE x
699 UNARY_EXPRESSION -
700 DECIMAL_LITERAL 5.0
702 :attribute value: the non-negative decimal value
703 :type: fractions.Fraction
704 """
705 def __init__(self, token, typ):
706 assert isinstance(token, Token)
707 assert token.kind == "DECIMAL"
708 assert isinstance(typ, Builtin_Decimal)
709 super().__init__(token.location, typ)
711 self.value = token.value
713 def dump(self, indent=0): # pragma: no cover
714 self.write_indent(indent, "Decimal Literal %u" % self.value)
716 def to_string(self):
717 return str(self.value)
719 def evaluate(self, mh, context):
720 assert isinstance(mh, Message_Handler)
721 assert context is None or isinstance(context, dict)
722 return Value(self.location, self.value, self.typ)
724 def to_python_object(self):
725 return float(self.value)
727 def can_be_null(self):
728 return False
731class String_Literal(Literal):
732 # lobster-trace: LRM.String_Values
733 # lobster-trace: LRM.Markup_String_Values
734 # lobster-trace: LRM.Primary
735 """String literals
737 Note the value of the string does not include the quotation marks,
738 and any escape sequences are fully resolved. For example::
740 "foo\\"bar"
742 Will have a value of ``foo"bar``.
744 :attribute value: string content
745 :type: str
747 :attribute references: resolved references of a markup string
748 :type: list[Record_Reference]
750 """
751 def __init__(self, token, typ):
752 assert isinstance(token, Token)
753 assert token.kind == "STRING"
754 assert isinstance(typ, Builtin_String)
755 super().__init__(token.location, typ)
757 self.value = token.value
758 self.has_references = isinstance(typ, Builtin_Markup_String)
759 self.references = []
761 def dump(self, indent=0): # pragma: no cover
762 self.write_indent(indent, "String Literal %s" % repr(self.value))
763 if self.has_references:
764 self.write_indent(indent + 1, "Markup References")
765 for ref in self.references:
766 ref.dump(indent + 2)
768 def to_string(self):
769 return self.value
771 def evaluate(self, mh, context):
772 assert isinstance(mh, Message_Handler)
773 assert context is None or isinstance(context, dict)
774 return Value(self.location, self.value, self.typ)
776 def to_python_object(self):
777 return self.value
779 def resolve_references(self, mh):
780 assert isinstance(mh, Message_Handler)
781 for ref in self.references:
782 ref.resolve_references(mh)
784 def can_be_null(self):
785 return False
788class Boolean_Literal(Literal):
789 # lobster-trace: LRM.Boolean_Values
790 # lobster-trace: LRM.Primary
791 """Boolean values
793 :attribute value: the boolean value
794 :type: bool
795 """
796 def __init__(self, token, typ):
797 assert isinstance(token, Token)
798 assert token.kind == "KEYWORD"
799 assert token.value in ("false", "true")
800 assert isinstance(typ, Builtin_Boolean)
801 super().__init__(token.location, typ)
803 self.value = token.value == "true"
805 def dump(self, indent=0): # pragma: no cover
806 self.write_indent(indent, "Boolean Literal %s" % self.value)
808 def to_string(self):
809 return str(self.value)
811 def evaluate(self, mh, context):
812 assert isinstance(mh, Message_Handler)
813 assert context is None or isinstance(context, dict)
814 return Value(self.location, self.value, self.typ)
816 def to_python_object(self):
817 return self.value
819 def can_be_null(self):
820 return False
823class Enumeration_Literal(Literal):
824 """Enumeration values
826 Note that this is distinct from
827 :class:`Enumeration_Literal_Spec`. An enumeration literal is a
828 specific mention of an enumeration member in an expression::
830 foo != my_enum.POTATO
831 ^^^^^^^^^^^^^^
833 To get to the string value of the enumeration literal
834 (i.e. ``POTATO`` here) you can get the name of the literal spec
835 itself: ``enum_lit.value.name``; and to get the name of the
836 enumeration (i.e. ``my_enum`` here) you can use
837 ``enum_lit.value.n_typ.name``.
839 :attribute value: enumeration value
840 :type: Enumeration_Literal_Spec
842 """
843 def __init__(self, location, literal):
844 # lobster-exclude: Constructor only declares variables
845 assert isinstance(literal, Enumeration_Literal_Spec)
846 super().__init__(location, literal.n_typ)
848 self.value = literal
850 def dump(self, indent=0): # pragma: no cover
851 # lobster-exclude: Debugging feature
852 self.write_indent(indent,
853 "Enumeration Literal %s.%s" % (self.typ.name,
854 self.value.name))
856 def to_string(self):
857 return self.typ.name + "." + self.value.name
859 def evaluate(self, mh, context):
860 assert isinstance(mh, Message_Handler)
861 assert context is None or isinstance(context, dict)
862 return Value(self.location, self.value, self.typ)
864 def to_python_object(self):
865 return self.value.name
867 def can_be_null(self):
868 return False
871class Array_Aggregate(Expression):
872 """Instances of array types
874 This is created when assigning to array components::
876 potatoes = ["picasso", "yukon gold", "sweet"]
877 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
879 The type of expression that can be found in an array is somewhat
880 limited:
882 * :class:`Literal`
883 * :class:`Array_Aggregate`
884 * :class:`Record_Reference`
886 :attribute value: contents of the array
887 :type: list[Expression]
889 """
890 def __init__(self, location, typ):
891 # lobster-trace: LRM.Record_Object_Declaration
893 super().__init__(location, typ)
894 self.value = []
896 def dump(self, indent=0): # pragma: no cover
897 # lobster-exclude: Debugging feature
898 self.write_indent(indent, "Array_Aggregate")
899 for n_value in self.value:
900 n_value.dump(indent + 1)
902 def append(self, value):
903 assert isinstance(value, (Literal,
904 Unary_Expression,
905 Array_Aggregate,
906 Tuple_Aggregate,
907 Record_Reference))
908 self.value.append(value)
910 def to_string(self):
911 return "[" + ", ".join(x.to_string() for x in self.value) + "]"
913 def evaluate(self, mh, context):
914 assert isinstance(mh, Message_Handler)
915 assert context is None or isinstance(context, dict)
916 return Value(self.location,
917 list(element.evaluate(mh, context)
918 for element in self.value),
919 self.typ)
921 def resolve_references(self, mh):
922 assert isinstance(mh, Message_Handler)
924 for val in self.value:
925 val.resolve_references(mh)
927 def to_python_object(self):
928 return [x.to_python_object() for x in self.value]
930 def can_be_null(self):
931 return False
934class Tuple_Aggregate(Expression):
935 """Instances of a tuple
937 This is created when assigning to a tuple components. There are
938 two forms, the ordinary form::
940 coordinate = (12.3, 40.0)
941 ^^^^^^^^^^^^
943 And the separator form::
945 item = 12345@42
946 ^^^^^^^^
948 In terms of AST there is no difference, as the separator is only
949 syntactic sugar.
951 :attribute value: contents of the tuple
952 :type: dict[str, Expression]
954 """
955 def __init__(self, location, typ):
956 # lobster-trace: LRM.Unspecified_Optional_Components
957 # lobster-trace: LRM.Record_Object_Declaration
959 super().__init__(location, typ)
960 self.value = {n_field.name : Implicit_Null(self, n_field)
961 for n_field in self.typ.components.values()}
963 def assign(self, field, value):
964 assert isinstance(field, str)
965 assert isinstance(value, (Literal,
966 Unary_Expression,
967 Tuple_Aggregate,
968 Record_Reference)), \
969 "value is %s" % value.__class__.__name__
970 assert field in self.typ.components
972 self.value[field] = value
974 def dump(self, indent=0): # pragma: no cover
975 # lobster-exclude: Debugging feature
976 self.write_indent(indent, "Tuple_Aggregate")
977 self.write_indent(indent + 1, "Type: %s" % self.typ.name)
978 for n_item in self.typ.iter_sequence():
979 if isinstance(n_item, Composite_Component):
980 self.value[n_item.name].dump(indent + 1)
982 def to_string(self):
983 first = True
984 if self.typ.has_separators():
985 rv = ""
986 else:
987 rv = "("
988 for n_item in self.typ.iter_sequence():
989 if isinstance(n_item, Separator):
990 rv += " %s " % n_item.token.value
991 elif first:
992 first = False
993 else:
994 rv += ", "
996 if isinstance(n_item, Composite_Component):
997 rv += self.value[n_item.name].to_string()
998 if self.typ.has_separators():
999 rv = ""
1000 else:
1001 rv = ")"
1002 return rv
1004 def evaluate(self, mh, context):
1005 assert isinstance(mh, Message_Handler)
1006 assert context is None or isinstance(context, dict)
1007 return Value(self.location,
1008 {name : element.evaluate(mh, context)
1009 for name, element in self.value.items()},
1010 self.typ)
1012 def resolve_references(self, mh):
1013 assert isinstance(mh, Message_Handler)
1015 for val in self.value.values():
1016 val.resolve_references(mh)
1018 def to_python_object(self):
1019 return {name: value.to_python_object()
1020 for name, value in self.value.items()}
1022 def can_be_null(self):
1023 return False
1026class Record_Reference(Expression):
1027 """Reference to another record object
1029 This can appear in record object declarations::
1031 Requirement Kitten {
1032 depends_on = Other_Package.Cat
1033 ^1 ^2
1034 }
1036 Note that this is distinct from :class:`Record_Object`. It is just
1037 the name; to get to the object referred to by this you can consult
1038 the target attribute.
1040 The reason we have this indirection is that not all names can be
1041 immediately resolved on parsing in the TRLC language.
1043 Note that while the containing package (see 1) is optional in the
1044 source language, the containing package will always be filled in
1045 in this AST node.
1047 :attribute name: The name of the record (see 2)
1048 :type: str
1050 :attribute target: The concrete record object referred to by (2)
1051 :type: Record_Object
1053 :attribute package: The package (see 1) supposed to contain (2)
1054 :type: Package
1056 """
1057 def __init__(self, location, name, typ, package):
1058 # lobster-exclude: Constructor only declares variables
1059 assert isinstance(location, Location)
1060 assert isinstance(name, str)
1061 assert isinstance(typ, Record_Type) or typ is None
1062 assert isinstance(package, Package)
1063 super().__init__(location, typ)
1065 self.name = name
1066 self.target = None
1067 self.package = package
1069 def dump(self, indent=0): # pragma: no cover
1070 # lobster-exclude: Debugging feature
1071 self.write_indent(indent, "Record Reference %s" % self.name)
1072 self.write_indent(indent + 1,
1073 "Resolved: %s" % (self.target is not None))
1075 def to_string(self):
1076 return self.name
1078 def evaluate(self, mh, context):
1079 assert isinstance(mh, Message_Handler)
1080 assert context is None or isinstance(context, dict)
1081 return Value(self.location, self, self.typ)
1083 def resolve_references(self, mh):
1084 # lobster-trace: LRM.References_To_Extensions
1085 assert isinstance(mh, Message_Handler)
1087 self.target = self.package.symbols.lookup_direct(
1088 mh = mh,
1089 name = self.name,
1090 error_location = self.location,
1091 required_subclass = Record_Object)
1092 if self.typ is None:
1093 self.typ = self.target.n_typ
1094 elif not self.target.n_typ.is_subclass_of(self.typ):
1095 mh.error(self.location,
1096 "expected reference of type %s, but %s is of type %s" %
1097 (self.typ.name,
1098 self.target.name,
1099 self.target.n_typ.name))
1101 def to_python_object(self):
1102 return self.target.fully_qualified_name()
1104 def can_be_null(self):
1105 return False
1108class Name_Reference(Expression):
1109 # lobster-trace: LRM.Qualified_Name
1110 # lobster-trace: LRM.Static_Regular_Expression
1112 """Reference to a name
1114 Name reference to either a :class:`Composite_Component` or a
1115 :class:`Quantified_Variable`. The actual value of course depends
1116 on the context. See :py:meth:`Expression.evaluate()`.
1118 For example::
1120 (forall x in potato => x > 1)
1121 ^1 ^2
1123 Both indicated parts are a :class:`Name_Reference`, the first one
1124 refers to a :class:`Composite_Component`, and the second refers to a
1125 :class:`Quantified_Variable`.
1127 :attribute entity: the entity named here
1128 :type: Composite_Component, Quantified_Variable
1129 """
1130 def __init__(self, location, entity):
1131 assert isinstance(entity, (Composite_Component,
1132 Quantified_Variable))
1133 super().__init__(location, entity.n_typ)
1134 self.entity = entity
1136 def dump(self, indent=0): # pragma: no cover
1137 self.write_indent(indent, "Name Reference to %s" % self.entity.name)
1139 def to_string(self):
1140 return self.entity.name
1142 def evaluate(self, mh, context):
1143 assert isinstance(mh, Message_Handler)
1144 assert context is None or isinstance(context, dict)
1146 if context is None:
1147 mh.error(self.location,
1148 "cannot be used in a static context")
1150 assert self.entity.name in context
1151 return context[self.entity.name].evaluate(mh, context)
1153 def can_be_null(self):
1154 # The only way we could generate null here (without raising
1155 # error earlier) is when we refer to a component that is
1156 # optional.
1157 if isinstance(self.entity, Composite_Component):
1158 return self.entity.optional
1159 else:
1160 return False
1163class Unary_Expression(Expression):
1164 """Expression with only one operand
1166 This captures the following operations:
1168 * Unary_Operator.PLUS (e.g. ``+5``)
1169 * Unary_Operator.MINUS (e.g. ``-5``)
1170 * Unary_Operator.ABSOLUTE_VALUE (e.g. ``abs 42``)
1171 * Unary_Operator.LOGICAL_NOT (e.g. ``not True``)
1172 * Unary_Operator.STRING_LENGTH (e.g. ``len("foobar")``)
1173 * Unary_Operator.ARRAY_LENGTH (e.g. ``len(component_name)``)
1174 * Unary_Operator.CONVERSION_TO_INT (e.g. ``Integer(5.3)``)
1175 * Unary_Operator.CONVERSION_TO_DECIMAL (e.g. ``Decimal(5)``)
1177 Note that several builtin functions are mapped to unary operators.
1179 :attribute operator: the operation
1180 :type: Unary_Operator
1182 :attribute n_operand: the expression we operate on
1183 :type: Expression
1185 """
1186 def __init__(self, mh, location, typ, operator, n_operand):
1187 # lobster-trace: LRM.Simple_Expression
1188 # lobster-trace: LRM.Relation
1189 # lobster-trace: LRM.Factor
1190 # lobster-trace: LRM.Signature_Len
1191 # lobster-trace: LRM.Signature_Type_Conversion
1193 super().__init__(location, typ)
1194 assert isinstance(mh, Message_Handler)
1195 assert isinstance(operator, Unary_Operator)
1196 assert isinstance(n_operand, Expression)
1197 self.operator = operator
1198 self.n_operand = n_operand
1200 if operator in (Unary_Operator.MINUS,
1201 Unary_Operator.PLUS,
1202 Unary_Operator.ABSOLUTE_VALUE):
1203 self.n_operand.ensure_type(mh, Builtin_Numeric_Type)
1204 elif operator == Unary_Operator.LOGICAL_NOT:
1205 self.n_operand.ensure_type(mh, Builtin_Boolean)
1206 elif operator == Unary_Operator.STRING_LENGTH:
1207 self.n_operand.ensure_type(mh, Builtin_String)
1208 elif operator == Unary_Operator.ARRAY_LENGTH:
1209 self.n_operand.ensure_type(mh, Array_Type)
1210 elif operator == Unary_Operator.CONVERSION_TO_INT:
1211 self.n_operand.ensure_type(mh, Builtin_Numeric_Type)
1212 elif operator == Unary_Operator.CONVERSION_TO_DECIMAL:
1213 self.n_operand.ensure_type(mh, Builtin_Numeric_Type)
1214 else:
1215 mh.ice_loc(self.location,
1216 "unexpected unary operation %s" % operator)
1218 def to_string(self):
1219 prefix_operators = {
1220 Unary_Operator.MINUS : "-",
1221 Unary_Operator.PLUS : "+",
1222 Unary_Operator.ABSOLUTE_VALUE : "abs ",
1223 Unary_Operator.LOGICAL_NOT : "not ",
1224 }
1225 function_calls = {
1226 Unary_Operator.STRING_LENGTH : "len",
1227 Unary_Operator.ARRAY_LENGTH : "len",
1228 Unary_Operator.CONVERSION_TO_INT : "Integer",
1229 Unary_Operator.CONVERSION_TO_DECIMAL : "Decimal"
1230 }
1232 if self.operator in prefix_operators:
1233 return prefix_operators[self.operator] + \
1234 self.n_operand.to_string()
1236 elif self.operator in function_calls:
1237 return "%s(%s)" % (function_calls[self.operator],
1238 self.n_operand.to_string())
1240 else:
1241 assert False
1243 def dump(self, indent=0): # pragma: no cover
1244 # lobster-exclude: Debugging feature
1245 self.write_indent(indent, "Unary %s Expression" % self.operator)
1246 self.write_indent(indent + 1, "Type: %s" % self.typ.name)
1247 self.n_operand.dump(indent + 1)
1249 def evaluate(self, mh, context):
1250 # lobster-trace: LRM.Null_Is_Invalid
1251 # lobster-trace: LRM.Signature_Len
1252 # lobster-trace: LRM.Signature_Type_Conversion
1253 # lobster-trace: LRM.Len_Semantics
1254 # lobster-trace: LRM.Integer_Conversion_Semantics
1255 # lobster-trace: LRM.Decimal_Conversion_Semantics
1257 assert isinstance(mh, Message_Handler)
1258 assert context is None or isinstance(context, dict)
1260 v_operand = self.n_operand.evaluate(mh, context)
1261 if v_operand.value is None: 1261 ↛ 1262line 1261 didn't jump to line 1262 because the condition on line 1261 was never true
1262 mh.error(v_operand.location,
1263 "input to unary expression %s (%s) must not be null" %
1264 (self.to_string(),
1265 mh.cross_file_reference(self.location)))
1267 if self.operator == Unary_Operator.MINUS:
1268 return Value(location = self.location,
1269 value = -v_operand.value,
1270 typ = self.typ)
1271 elif self.operator == Unary_Operator.PLUS:
1272 return Value(location = self.location,
1273 value = +v_operand.value,
1274 typ = self.typ)
1275 elif self.operator == Unary_Operator.LOGICAL_NOT:
1276 return Value(location = self.location,
1277 value = not v_operand.value,
1278 typ = self.typ)
1279 elif self.operator == Unary_Operator.ABSOLUTE_VALUE:
1280 return Value(location = self.location,
1281 value = abs(v_operand.value),
1282 typ = self.typ)
1283 elif self.operator in (Unary_Operator.STRING_LENGTH,
1284 Unary_Operator.ARRAY_LENGTH):
1285 return Value(location = self.location,
1286 value = len(v_operand.value),
1287 typ = self.typ)
1288 elif self.operator == Unary_Operator.CONVERSION_TO_INT:
1289 if isinstance(v_operand.value, Fraction): 1289 ↛ 1295line 1289 didn't jump to line 1295 because the condition on line 1289 was always true
1290 return Value(
1291 location = self.location,
1292 value = math.round_nearest_away(v_operand.value),
1293 typ = self.typ)
1294 else:
1295 return Value(location = self.location,
1296 value = v_operand.value,
1297 typ = self.typ)
1298 elif self.operator == Unary_Operator.CONVERSION_TO_DECIMAL:
1299 return Value(location = self.location,
1300 value = Fraction(v_operand.value),
1301 typ = self.typ)
1302 else:
1303 mh.ice_loc(self.location,
1304 "unexpected unary operation %s" % self.operator)
1306 def to_python_object(self):
1307 assert self.operator in (Unary_Operator.MINUS,
1308 Unary_Operator.PLUS)
1309 val = self.n_operand.to_python_object()
1310 if self.operator == Unary_Operator.MINUS:
1311 return -val
1312 else:
1313 return val
1315 def can_be_null(self):
1316 return False
1319class Binary_Expression(Expression):
1320 """Expression with two operands
1322 This captures the following operations:
1324 * Binary_Operator.LOGICAL_AND (e.g. ``a and b``)
1325 * Binary_Operator.LOGICAL_OR (e.g. ``a or b``)
1326 * Binary_Operator.LOGICAL_XOR (e.g. ``a xor b``)
1327 * Binary_Operator.LOGICAL_IMPLIES (e.g. ``a implies b``)
1328 * Binary_Operator.COMP_EQ (e.g. ``a == null``)
1329 * Binary_Operator.COMP_NEQ (e.g. ``a != null``)
1330 * Binary_Operator.COMP_LT (e.g. ``1 < 2``)
1331 * Binary_Operator.COMP_LEQ (e.g. ``1 <= 2``)
1332 * Binary_Operator.COMP_GT (e.g. ``a > b``)
1333 * Binary_Operator.COMP_GEQ (e.g. ``a >= b``)
1334 * Binary_Operator.STRING_CONTAINS (e.g. ``"foo" in "foobar"``)
1335 * Binary_Operator.STRING_STARTSWITH (e.g. ``startswith("foo", "f")``)
1336 * Binary_Operator.STRING_ENDSWITH (e.g. ``endswith("foo", "o")``)
1337 * Binary_Operator.STRING_REGEX (e.g. ``matches("foo", ".o.``)
1338 * Binary_Operator.ARRAY_CONTAINS (e.g. ``42 in arr``)
1339 * Binary_Operator.PLUS (e.g. ``42 + b`` or ``"foo" + bar``)
1340 * Binary_Operator.MINUS (e.g. ``a - 1``)
1341 * Binary_Operator.TIMES (e.g. ``2 * x``)
1342 * Binary_Operator.DIVIDE (e.g. ``x / 2``)
1343 * Binary_Operator.REMAINDER (e.g. ``x % 2``)
1344 * Binary_Operator.POWER (e.g. ``x ** 2``)
1345 * Binary_Operator.INDEX (e.g. ``foo[2]``)
1347 Note that several builtin functions are mapped to unary operators.
1349 Note also that the plus operation is supported for integers,
1350 rationals and strings.
1352 :attribute operator: the operation
1353 :type: Binary_Operator
1355 :attribute n_lhs: the first operand
1356 :type: Expression
1358 :attribute n_lhs: the second operand
1359 :type: Expression
1361 """
1362 def __init__(self, mh, location, typ, operator, n_lhs, n_rhs):
1363 # lobster-trace: LRM.Expression
1364 # lobster-trace: LRM.Relation
1365 # lobster-trace: LRM.Simple_Expression
1366 # lobster-trace: LRM.Term
1367 # lobster-trace: LRM.Factor
1368 # lobster-trace: LRM.Signature_String_End_Functions
1369 # lobster-trace: LRM.Signature_Matches
1371 super().__init__(location, typ)
1372 assert isinstance(mh, Message_Handler)
1373 assert isinstance(operator, Binary_Operator)
1374 assert isinstance(n_lhs, Expression)
1375 assert isinstance(n_rhs, Expression)
1376 self.operator = operator
1377 self.n_lhs = n_lhs
1378 self.n_rhs = n_rhs
1380 if operator in (Binary_Operator.LOGICAL_AND,
1381 Binary_Operator.LOGICAL_OR,
1382 Binary_Operator.LOGICAL_XOR,
1383 Binary_Operator.LOGICAL_IMPLIES):
1384 self.n_lhs.ensure_type(mh, Builtin_Boolean)
1385 self.n_rhs.ensure_type(mh, Builtin_Boolean)
1387 elif operator in (Binary_Operator.COMP_EQ,
1388 Binary_Operator.COMP_NEQ):
1389 if (self.n_lhs.typ is None) or (self.n_rhs.typ is None):
1390 # We can compary anything to null (including itself)
1391 pass
1392 elif self.n_lhs.typ != self.n_rhs.typ:
1393 # Otherwise we can compare anything, as long as the
1394 # types match
1395 mh.error(self.location,
1396 "type mismatch: %s and %s do not match" %
1397 (self.n_lhs.typ.name,
1398 self.n_rhs.typ.name))
1400 elif operator in (Binary_Operator.COMP_LT,
1401 Binary_Operator.COMP_LEQ,
1402 Binary_Operator.COMP_GT,
1403 Binary_Operator.COMP_GEQ):
1404 self.n_lhs.ensure_type(mh, Builtin_Numeric_Type)
1405 self.n_rhs.ensure_type(mh, self.n_lhs.typ)
1407 elif operator in (Binary_Operator.STRING_CONTAINS,
1408 Binary_Operator.STRING_STARTSWITH,
1409 Binary_Operator.STRING_ENDSWITH,
1410 Binary_Operator.STRING_REGEX):
1411 self.n_lhs.ensure_type(mh, Builtin_String)
1412 self.n_rhs.ensure_type(mh, Builtin_String)
1414 elif operator == Binary_Operator.ARRAY_CONTAINS:
1415 self.n_rhs.ensure_type(mh, Array_Type)
1416 self.n_lhs.ensure_type(mh, self.n_rhs.typ.element_type.__class__)
1418 elif operator == Binary_Operator.PLUS:
1419 if isinstance(self.n_lhs.typ, Builtin_Numeric_Type):
1420 self.n_rhs.ensure_type(mh, self.n_lhs.typ)
1421 else:
1422 self.n_lhs.ensure_type(mh, Builtin_String)
1423 self.n_rhs.ensure_type(mh, Builtin_String)
1425 elif operator in (Binary_Operator.MINUS,
1426 Binary_Operator.TIMES,
1427 Binary_Operator.DIVIDE):
1428 self.n_lhs.ensure_type(mh, Builtin_Numeric_Type)
1429 self.n_rhs.ensure_type(mh, self.n_lhs.typ)
1431 elif operator == Binary_Operator.POWER:
1432 self.n_lhs.ensure_type(mh, Builtin_Numeric_Type)
1433 self.n_rhs.ensure_type(mh, Builtin_Integer)
1435 elif operator == Binary_Operator.REMAINDER:
1436 self.n_lhs.ensure_type(mh, Builtin_Integer)
1437 self.n_rhs.ensure_type(mh, Builtin_Integer)
1439 elif operator == Binary_Operator.INDEX:
1440 self.n_lhs.ensure_type(mh, Array_Type)
1441 self.n_rhs.ensure_type(mh, Builtin_Integer)
1443 else:
1444 mh.ice_loc(self.location,
1445 "unexpected binary operation %s" % operator)
1447 def dump(self, indent=0): # pragma: no cover
1448 # lobster-exclude: Debugging feature
1449 self.write_indent(indent, "Binary %s Expression" % self.operator)
1450 self.write_indent(indent + 1, "Type: %s" % self.typ.name)
1451 self.n_lhs.dump(indent + 1)
1452 self.n_rhs.dump(indent + 1)
1454 def to_string(self):
1455 infix_operators = {
1456 Binary_Operator.LOGICAL_AND : "and",
1457 Binary_Operator.LOGICAL_OR : "or",
1458 Binary_Operator.LOGICAL_XOR : "xor",
1459 Binary_Operator.LOGICAL_IMPLIES : "implies",
1460 Binary_Operator.COMP_EQ : "==",
1461 Binary_Operator.COMP_NEQ : "!=",
1462 Binary_Operator.COMP_LT : "<",
1463 Binary_Operator.COMP_LEQ : "<=",
1464 Binary_Operator.COMP_GT : ">",
1465 Binary_Operator.COMP_GEQ : ">=",
1466 Binary_Operator.STRING_CONTAINS : "in",
1467 Binary_Operator.ARRAY_CONTAINS : "in",
1468 Binary_Operator.PLUS : "+",
1469 Binary_Operator.MINUS : "-",
1470 Binary_Operator.TIMES : "*",
1471 Binary_Operator.DIVIDE : "/",
1472 Binary_Operator.REMAINDER : "%",
1473 Binary_Operator.POWER : "**",
1474 }
1475 string_functions = {
1476 Binary_Operator.STRING_STARTSWITH : "startswith",
1477 Binary_Operator.STRING_ENDSWITH : "endswith",
1478 Binary_Operator.STRING_REGEX : "matches",
1479 }
1481 if self.operator in infix_operators:
1482 return "%s %s %s" % (self.n_lhs.to_string(),
1483 infix_operators[self.operator],
1484 self.n_rhs.to_string())
1486 elif self.operator in string_functions:
1487 return "%s(%s, %s)" % (string_functions[self.operator],
1488 self.n_lhs.to_string(),
1489 self.n_rhs.to_string())
1491 elif self.operator == Binary_Operator.INDEX:
1492 return "%s[%s]" % (self.n_lhs.to_string(),
1493 self.n_rhs.to_string())
1495 else:
1496 assert False
1498 def evaluate(self, mh, context):
1499 # lobster-trace: LRM.Null_Equivalence
1500 # lobster-trace: LRM.Null_Is_Invalid
1501 # lobster-trace: LRM.Signature_String_End_Functions
1502 # lobster-trace: LRM.Signature_Matches
1503 # lobster-trace: LRM.Startswith_Semantics
1504 # lobster-trace: LRM.Endswith_Semantics
1505 # lobster-trace: LRM.Matches_Semantics
1507 assert isinstance(mh, Message_Handler)
1508 assert context is None or isinstance(context, dict)
1510 v_lhs = self.n_lhs.evaluate(mh, context)
1511 if v_lhs.value is None and \
1512 self.operator not in (Binary_Operator.COMP_EQ,
1513 Binary_Operator.COMP_NEQ):
1514 mh.error(v_lhs.location,
1515 "lhs of check %s (%s) must not be null" %
1516 (self.to_string(),
1517 mh.cross_file_reference(self.location)))
1519 # Check for the short-circuit operators first
1520 if self.operator == Binary_Operator.LOGICAL_AND:
1521 assert isinstance(v_lhs.value, bool)
1522 if v_lhs.value:
1523 return self.n_rhs.evaluate(mh, context)
1524 else:
1525 return v_lhs
1527 elif self.operator == Binary_Operator.LOGICAL_OR:
1528 assert isinstance(v_lhs.value, bool)
1529 if v_lhs.value:
1530 return v_lhs
1531 else:
1532 return self.n_rhs.evaluate(mh, context)
1534 elif self.operator == Binary_Operator.LOGICAL_IMPLIES:
1535 assert isinstance(v_lhs.value, bool)
1536 if v_lhs.value:
1537 return self.n_rhs.evaluate(mh, context)
1538 else:
1539 return Value(location = self.location,
1540 value = True,
1541 typ = self.typ)
1543 # Otherwise, evaluate RHS and do the operation
1544 v_rhs = self.n_rhs.evaluate(mh, context)
1545 if v_rhs.value is None and \
1546 self.operator not in (Binary_Operator.COMP_EQ,
1547 Binary_Operator.COMP_NEQ):
1548 mh.error(v_rhs.location,
1549 "rhs of check %s (%s) must not be null" %
1550 (self.to_string(),
1551 mh.cross_file_reference(self.location)))
1553 if self.operator == Binary_Operator.LOGICAL_XOR:
1554 assert isinstance(v_lhs.value, bool)
1555 assert isinstance(v_rhs.value, bool)
1556 return Value(location = self.location,
1557 value = v_lhs.value ^ v_rhs.value,
1558 typ = self.typ)
1560 elif self.operator == Binary_Operator.COMP_EQ:
1561 return Value(location = self.location,
1562 value = v_lhs.value == v_rhs.value,
1563 typ = self.typ)
1565 elif self.operator == Binary_Operator.COMP_NEQ:
1566 return Value(location = self.location,
1567 value = v_lhs.value != v_rhs.value,
1568 typ = self.typ)
1570 elif self.operator in (Binary_Operator.COMP_LT,
1571 Binary_Operator.COMP_LEQ,
1572 Binary_Operator.COMP_GT,
1573 Binary_Operator.COMP_GEQ):
1574 return Value(
1575 location = self.location,
1576 value = {
1577 Binary_Operator.COMP_LT : lambda lhs, rhs: lhs < rhs,
1578 Binary_Operator.COMP_LEQ : lambda lhs, rhs: lhs <= rhs,
1579 Binary_Operator.COMP_GT : lambda lhs, rhs: lhs > rhs,
1580 Binary_Operator.COMP_GEQ : lambda lhs, rhs: lhs >= rhs,
1581 }[self.operator](v_lhs.value, v_rhs.value),
1582 typ = self.typ)
1584 elif self.operator == Binary_Operator.STRING_CONTAINS:
1585 assert isinstance(v_lhs.value, str)
1586 assert isinstance(v_rhs.value, str)
1588 return Value(location = self.location,
1589 value = v_lhs.value in v_rhs.value,
1590 typ = self.typ)
1592 elif self.operator == Binary_Operator.STRING_STARTSWITH:
1593 assert isinstance(v_lhs.value, str)
1594 assert isinstance(v_rhs.value, str)
1595 return Value(location = self.location,
1596 value = v_lhs.value.startswith(v_rhs.value),
1597 typ = self.typ)
1599 elif self.operator == Binary_Operator.STRING_ENDSWITH:
1600 assert isinstance(v_lhs.value, str)
1601 assert isinstance(v_rhs.value, str)
1602 return Value(location = self.location,
1603 value = v_lhs.value.endswith(v_rhs.value),
1604 typ = self.typ)
1606 elif self.operator == Binary_Operator.STRING_REGEX:
1607 assert isinstance(v_lhs.value, str)
1608 assert isinstance(v_rhs.value, str)
1609 return Value(location = self.location,
1610 value = re.match(v_rhs.value,
1611 v_lhs.value) is not None,
1612 typ = self.typ)
1614 elif self.operator == Binary_Operator.ARRAY_CONTAINS:
1615 assert isinstance(v_rhs.value, list)
1617 return Value(location = self.location,
1618 value = v_lhs in v_rhs.value,
1619 typ = self.typ)
1621 elif self.operator == Binary_Operator.PLUS:
1622 assert isinstance(v_lhs.value, (int, str, Fraction))
1623 assert isinstance(v_rhs.value, (int, str, Fraction))
1624 return Value(location = self.location,
1625 value = v_lhs.value + v_rhs.value,
1626 typ = self.typ)
1628 elif self.operator == Binary_Operator.MINUS:
1629 assert isinstance(v_lhs.value, (int, Fraction))
1630 assert isinstance(v_rhs.value, (int, Fraction))
1631 return Value(location = self.location,
1632 value = v_lhs.value - v_rhs.value,
1633 typ = self.typ)
1635 elif self.operator == Binary_Operator.TIMES:
1636 assert isinstance(v_lhs.value, (int, Fraction))
1637 assert isinstance(v_rhs.value, (int, Fraction))
1638 return Value(location = self.location,
1639 value = v_lhs.value * v_rhs.value,
1640 typ = self.typ)
1642 elif self.operator == Binary_Operator.DIVIDE:
1643 assert isinstance(v_lhs.value, (int, Fraction))
1644 assert isinstance(v_rhs.value, (int, Fraction))
1646 if v_rhs.value == 0: 1646 ↛ 1647line 1646 didn't jump to line 1647 because the condition on line 1646 was never true
1647 mh.error(v_rhs.location,
1648 "division by zero in %s (%s)" %
1649 (self.to_string(),
1650 mh.cross_file_reference(self.location)))
1652 if isinstance(v_lhs.value, int):
1653 return Value(location = self.location,
1654 value = v_lhs.value // v_rhs.value,
1655 typ = self.typ)
1656 else:
1657 return Value(location = self.location,
1658 value = v_lhs.value / v_rhs.value,
1659 typ = self.typ)
1661 elif self.operator == Binary_Operator.REMAINDER:
1662 assert isinstance(v_lhs.value, int)
1663 assert isinstance(v_rhs.value, int)
1665 if v_rhs.value == 0: 1665 ↛ 1666line 1665 didn't jump to line 1666 because the condition on line 1665 was never true
1666 mh.error(v_rhs.location,
1667 "division by zero in %s (%s)" %
1668 (self.to_string(),
1669 mh.cross_file_reference(self.location)))
1671 return Value(location = self.location,
1672 value = math.remainder(v_lhs.value, v_rhs.value),
1673 typ = self.typ)
1675 elif self.operator == Binary_Operator.POWER:
1676 assert isinstance(v_lhs.value, (int, Fraction))
1677 assert isinstance(v_rhs.value, int)
1678 return Value(location = self.location,
1679 value = v_lhs.value ** v_rhs.value,
1680 typ = self.typ)
1682 elif self.operator == Binary_Operator.INDEX:
1683 assert isinstance(v_lhs.value, list)
1684 assert isinstance(v_rhs.value, int)
1686 if v_rhs.value < 0: 1686 ↛ 1687line 1686 didn't jump to line 1687 because the condition on line 1686 was never true
1687 mh.error(v_rhs.location,
1688 "index cannot be less than zero in %s (%s)" %
1689 (self.to_string(),
1690 mh.cross_file_reference(self.location)))
1691 elif v_lhs.typ.upper_bound is not None and \ 1691 ↛ 1693line 1691 didn't jump to line 1693 because the condition on line 1691 was never true
1692 v_rhs.value > v_lhs.typ.upper_bound:
1693 mh.error(v_rhs.location,
1694 "index cannot be more than %u in %s (%s)" %
1695 (v_lhs.typ.upper_bound,
1696 self.to_string(),
1697 mh.cross_file_reference(self.location)))
1698 elif v_rhs.value > len(v_lhs.value): 1698 ↛ 1699line 1698 didn't jump to line 1699 because the condition on line 1698 was never true
1699 mh.error(v_lhs.location,
1700 "array is not big enough in %s (%s)" %
1701 (self.to_string(),
1702 mh.cross_file_reference(self.location)))
1704 return Value(location = self.location,
1705 value = v_lhs.value[v_rhs.value].value,
1706 typ = self.typ)
1708 else:
1709 mh.ice_loc(self.location,
1710 "unexpected binary operator %s" % self.operator)
1712 def can_be_null(self):
1713 return False
1716class Field_Access_Expression(Expression):
1717 """Tuple field access
1719 For example in::
1721 foo.bar
1722 ^1 ^2
1724 :attribute n_prefix: expression with tuple type (see 1)
1725 :type: Expression
1727 :attribute n_field: a tuple field to dereference (see 2)
1728 :type: Composite_Component
1730 """
1731 def __init__(self, mh, location, n_prefix, n_field):
1732 assert isinstance(mh, Message_Handler)
1733 assert isinstance(n_prefix, Expression)
1734 assert isinstance(n_field, Composite_Component)
1735 super().__init__(location, n_field.n_typ)
1736 self.n_prefix = n_prefix
1737 self.n_field = n_field
1739 self.n_prefix.ensure_type(mh, self.n_field.member_of)
1741 def dump(self, indent=0): # pragma: no cover
1742 # lobster-exclude: Debugging feature
1743 self.write_indent(indent, "Field_Access (%s)" % self.n_field.name)
1744 self.n_prefix.dump(indent + 1)
1746 def to_string(self):
1747 return self.n_prefix.to_string() + "." + self.n_field.name
1749 def evaluate(self, mh, context):
1750 assert isinstance(mh, Message_Handler)
1751 assert context is None or isinstance(context, dict)
1753 return self.n_prefix.evaluate(mh, context).value[self.n_field.name]
1755 def can_be_null(self):
1756 return False
1759class Range_Test(Expression):
1760 """Range membership test
1762 For example in::
1764 x in 1 .. field+1
1765 ^lhs ^lower ^^^^^^^upper
1767 Note that none of these are guaranteed to be literals or names;
1768 you can have arbitrarily complex expressions here.
1770 :attribute n_lhs: the expression to test
1771 :type: Expression
1773 :attribute n_lower: the lower bound
1774 :type: Expression
1776 :attribute n_lower: the upper bound
1777 :type: Expression
1779 """
1780 def __init__(self, mh, location, typ, n_lhs, n_lower, n_upper):
1781 # lobster-trace: LRM.Relation
1782 super().__init__(location, typ)
1783 assert isinstance(mh, Message_Handler)
1784 assert isinstance(n_lhs, Expression)
1785 assert isinstance(n_lower, Expression)
1786 assert isinstance(n_upper, Expression)
1787 self.n_lhs = n_lhs
1788 self.n_lower = n_lower
1789 self.n_upper = n_upper
1791 self.n_lhs.ensure_type(mh, Builtin_Numeric_Type)
1792 self.n_lower.ensure_type(mh, self.n_lhs.typ)
1793 self.n_upper.ensure_type(mh, self.n_lhs.typ)
1795 def to_string(self):
1796 return "%s in %s .. %s" % (self.n_lhs.to_string(),
1797 self.n_lower.to_string(),
1798 self.n_upper.to_string())
1800 def dump(self, indent=0): # pragma: no cover
1801 # lobster-exclude: Debugging feature
1802 self.write_indent(indent, "Range Test")
1803 self.write_indent(indent + 1, "Type: %s" % self.typ)
1804 self.n_lhs.dump(indent + 1)
1805 self.n_lower.dump(indent + 1)
1806 self.n_upper.dump(indent + 1)
1808 def evaluate(self, mh, context):
1809 # lobster-trace: LRM.Null_Is_Invalid
1810 assert isinstance(mh, Message_Handler)
1811 assert context is None or isinstance(context, dict)
1813 v_lhs = self.n_lhs.evaluate(mh, context)
1814 if v_lhs.value is None: 1814 ↛ 1815line 1814 didn't jump to line 1815 because the condition on line 1814 was never true
1815 mh.error(v_lhs.location,
1816 "lhs of range check %s (%s) see must not be null" %
1817 (self.to_string(),
1818 mh.cross_file_reference(self.location)))
1820 v_lower = self.n_lower.evaluate(mh, context)
1821 if v_lower.value is None: 1821 ↛ 1822line 1821 didn't jump to line 1822 because the condition on line 1821 was never true
1822 mh.error(v_lower.location,
1823 "lower bound of range check %s (%s) must not be null" %
1824 (self.to_string(),
1825 mh.cross_file_reference(self.location)))
1827 v_upper = self.n_upper.evaluate(mh, context)
1828 if v_upper.value is None: 1828 ↛ 1829line 1828 didn't jump to line 1829 because the condition on line 1828 was never true
1829 mh.error(v_upper.location,
1830 "upper bound of range check %s (%s) must not be null" %
1831 (self.to_string(),
1832 mh.cross_file_reference(self.location)))
1834 return Value(location = self.location,
1835 value = v_lower.value <= v_lhs.value <= v_upper.value,
1836 typ = self.typ)
1838 def can_be_null(self):
1839 return False
1842class OneOf_Expression(Expression):
1843 """OneOf expression
1845 For example in::
1847 oneof(a, b, c)
1848 ^^^^^^^ choices
1850 :attribute choices: a list of boolean expressions to test
1851 :type: list[Expression]
1852 """
1853 def __init__(self, mh, location, typ, choices):
1854 # lobster-trace: LRM.Signature_OneOf
1855 super().__init__(location, typ)
1856 assert isinstance(typ, Builtin_Boolean)
1857 assert isinstance(mh, Message_Handler)
1858 assert isinstance(choices, list)
1859 assert all(isinstance(item, Expression)
1860 for item in choices)
1861 self.choices = choices
1863 for n_choice in choices:
1864 n_choice.ensure_type(mh, Builtin_Boolean)
1866 def to_string(self):
1867 return "oneof(%s)" % ", ".join(n_choice.to_string()
1868 for n_choice in self.choices)
1870 def dump(self, indent=0): # pragma: no cover
1871 # lobster-exclude: Debugging feature
1872 self.write_indent(indent, "OneOf Test")
1873 self.write_indent(indent + 1, "Type: %s" % self.typ)
1874 for n_choice in self.choices:
1875 n_choice.dump(indent + 1)
1877 def evaluate(self, mh, context):
1878 # lobster-trace: LRM.OneOf_Semantics
1879 assert isinstance(mh, Message_Handler)
1880 assert context is None or isinstance(context, dict)
1882 v_choices = [n_choice.evaluate(mh, context).value
1883 for n_choice in self.choices]
1885 return Value(location = self.location,
1886 value = v_choices.count(True) == 1,
1887 typ = self.typ)
1889 def can_be_null(self):
1890 return False
1893class Action(Node):
1894 """An if or elseif part inside a conditional expression
1896 Each :class:`Conditional_Expression` is made up of a sequence of
1897 Actions. For example here is a single expression with two
1898 Actions::
1900 (if x == 0 then "zero" elsif x == 1 then "one" else "lots")
1901 ^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^
1903 Note that the else part is not an action, it is an attribute of
1904 the :class:`Conditional_Expression` itself.
1906 :attribute kind: Either if or elseif
1907 :type: str
1909 :attribute n_cond: The boolean condition expression
1910 :type: Expression
1912 :attribute n_expr: The value if the condition evaluates to true
1913 :type: Expression
1915 """
1916 def __init__(self, mh, t_kind, n_condition, n_expression):
1917 # lobster-trace: LRM.Conditional_Expression
1918 assert isinstance(mh, Message_Handler)
1919 assert isinstance(t_kind, Token)
1920 assert t_kind.kind == "KEYWORD"
1921 assert t_kind.value in ("if", "elsif")
1922 assert isinstance(n_condition, Expression)
1923 assert isinstance(n_expression, Expression)
1924 super().__init__(t_kind.location)
1925 self.kind = t_kind.value
1926 self.n_cond = n_condition
1927 self.n_expr = n_expression
1928 # lobster-trace: LRM.Conditional_Expression_Types
1929 self.n_cond.ensure_type(mh, Builtin_Boolean)
1931 def dump(self, indent=0): # pragma: no cover
1932 # lobster-exclude: Debugging feature
1933 self.write_indent(indent, "%s Action" % self.kind.capitalize())
1934 self.write_indent(indent + 1, "Condition")
1935 self.n_cond.dump(indent + 2)
1936 self.write_indent(indent + 1, "Value")
1937 self.n_expr.dump(indent + 2)
1939 def to_string(self):
1940 return "%s %s then %s" % (self.kind,
1941 self.n_cond.to_string(),
1942 self.n_expr.to_string())
1945class Conditional_Expression(Expression):
1946 """A conditional expression
1948 Each :class:`Conditional_Expression` is made up of a sequence of
1949 one or more :class:`Action`. For example here is a single
1950 expression with two Actions::
1952 (if x == 0 then "zero" elsif x == 1 then "one" else "lots")
1953 ^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^
1955 The else expression is part of the conditional expression itself.
1957 A conditional expression will have at least one action (the if
1958 action), and all other actions will be elsif actions. The else
1959 expression is not optional and will always be present. The types
1960 of all actions and the else expression will match.
1962 :attribute actions: a list of Actions
1963 :type: list[Action]
1965 :attribute else_expr: the else expression
1966 :type: Expression
1968 """
1969 def __init__(self, location, if_action):
1970 # lobster-trace: LRM.Conditional_Expression
1971 assert isinstance(if_action, Action)
1972 assert if_action.kind == "if"
1973 super().__init__(location, if_action.n_expr.typ)
1974 self.actions = [if_action]
1975 self.else_expr = None
1977 def add_elsif(self, mh, n_action):
1978 # lobster-trace: LRM.Conditional_Expression
1979 # lobster-trace; LRM.Conditional_Expression_Types
1980 assert isinstance(mh, Message_Handler)
1981 assert isinstance(n_action, Action)
1982 assert n_action.kind == "elsif"
1984 n_action.n_expr.ensure_type(mh, self.typ)
1985 self.actions.append(n_action)
1987 def set_else_part(self, mh, n_expr):
1988 # lobster-trace: LRM.Conditional_Expression
1989 # lobster-trace; LRM.Conditional_Expression_Types
1990 assert isinstance(mh, Message_Handler)
1991 assert isinstance(n_expr, Expression)
1993 n_expr.ensure_type(mh, self.typ)
1994 self.else_expr = n_expr
1996 def dump(self, indent=0): # pragma: no cover
1997 # lobster-exclude: Debugging feature
1998 self.write_indent(indent, "Conditional expression")
1999 for action in self.actions:
2000 action.dump(indent + 1)
2001 self.write_indent(indent + 1, "Else")
2002 self.else_expr.dump(indent + 2)
2004 def to_string(self):
2005 rv = "(" + " ".join(action.to_string()
2006 for action in self.actions)
2007 rv += " else %s" % self.else_expr.to_string()
2008 rv += ")"
2009 return rv
2011 def evaluate(self, mh, context):
2012 # lobster-trace: LRM.Conditional_Expression_Else
2013 # lobster-trace: LRM.Conditional_Expression_Evaluation
2014 # lobster-trace: LRM.Null_Is_Invalid
2015 assert isinstance(mh, Message_Handler)
2016 assert context is None or isinstance(context, dict)
2018 for action in self.actions:
2019 v_cond = action.n_cond.evaluate(mh, context)
2020 if v_cond.value is None: 2020 ↛ 2021line 2020 didn't jump to line 2021 because the condition on line 2020 was never true
2021 mh.error(v_cond.location,
2022 "condition of %s (%s) must not be null" %
2023 (action.to_string(),
2024 mh.cross_file_reference(self.location)))
2025 if v_cond.value:
2026 return action.n_expr.evaluate(mh, context)
2028 return self.else_expr.evaluate(mh, context)
2030 def can_be_null(self):
2031 if self.else_expr and self.else_expr.can_be_null():
2032 return True
2034 return any(action.n_expr.can_be_null()
2035 for action in self.actions)
2038class Quantified_Expression(Expression):
2039 """A quantified expression
2041 For example::
2043 (forall x in array_component => x > 0)
2044 ^4 ^1 ^2 ^^^^^3
2046 A quantified expression introduces and binds a
2047 :class:`Quantified_Variable` (see 1) from a specified source (see
2048 2). When the body (see 3) is evaluated, the name of 1 is bound to
2049 each component of the source in turn.
2051 :attribute n_var: The quantified variable (see 1)
2052 :type: Quantified_Variable
2054 :attribute n_source: The array to iterate over (see 2)
2055 :type: Name_Reference
2057 :attribute n_expr: The body of the quantifier (see 3)
2058 :type: Expression
2060 :attribute universal: True means forall, false means exists (see 4)
2061 :type: Boolean
2063 """
2064 def __init__(self, mh, location,
2065 typ,
2066 universal,
2067 n_variable,
2068 n_source,
2069 n_expr):
2070 # lobster-trace: LRM.Quantified_Expression
2071 # lobster-trace: LRM.Quantification_Type
2072 super().__init__(location, typ)
2073 assert isinstance(typ, Builtin_Boolean)
2074 assert isinstance(universal, bool)
2075 assert isinstance(n_variable, Quantified_Variable)
2076 assert isinstance(n_expr, Expression)
2077 assert isinstance(n_source, Name_Reference)
2078 self.universal = universal
2079 self.n_var = n_variable
2080 self.n_expr = n_expr
2081 self.n_source = n_source
2082 self.n_expr.ensure_type(mh, Builtin_Boolean)
2084 def dump(self, indent=0): # pragma: no cover
2085 # lobster-exclude: Debugging feature
2086 if self.universal:
2087 self.write_indent(indent, "Universal quantified expression")
2088 else:
2089 self.write_indent(indent, "Existential quantified expression")
2090 self.n_var.dump(indent + 1)
2091 self.n_expr.dump(indent + 1)
2093 def to_string(self):
2094 return "(%s %s in %s => %s)" % ("forall"
2095 if self.universal
2096 else "exists",
2097 self.n_var.name,
2098 self.n_source.to_string(),
2099 self.n_expr.to_string())
2101 def evaluate(self, mh, context):
2102 # lobster-trace: LRM.Null_Is_Invalid
2103 # lobster-trace: LRM.Universal_Quantification_Semantics
2104 # lobster-trace: LRM.Existential_Quantification_Semantics
2105 assert isinstance(mh, Message_Handler)
2106 assert context is None or isinstance(context, dict)
2108 if context is None: 2108 ↛ 2109line 2108 didn't jump to line 2109 because the condition on line 2108 was never true
2109 new_ctx = {}
2110 else:
2111 new_ctx = copy(context)
2113 # This is going to be a bit tricky. We essentially eliminate
2114 # the quantifier and substitute; for the sake of making better
2115 # error messages.
2116 assert isinstance(self.n_source.entity, Composite_Component)
2117 array_values = context[self.n_source.entity.name]
2118 if isinstance(array_values, Implicit_Null):
2119 mh.error(array_values.location,
2120 "%s in quantified expression %s (%s) "
2121 "must not be null" %
2122 (self.n_source.to_string(),
2123 self.to_string(),
2124 mh.cross_file_reference(self.location)))
2125 else:
2126 assert isinstance(array_values, Array_Aggregate)
2128 rv = self.universal
2129 loc = self.location
2130 for binding in array_values.value:
2131 new_ctx[self.n_var.name] = binding
2132 result = self.n_expr.evaluate(mh, new_ctx)
2133 assert isinstance(result.value, bool)
2134 if self.universal and not result.value:
2135 rv = False
2136 loc = binding.location
2137 break
2138 elif not self.universal and result.value:
2139 rv = True
2140 loc = binding.location
2141 break
2143 return Value(location = loc,
2144 value = rv,
2145 typ = self.typ)
2147 def can_be_null(self):
2148 return False
2151##############################################################################
2152# AST Nodes (Entities)
2153##############################################################################
2155class Entity(Node, metaclass=ABCMeta):
2156 """Base class for all entities.
2158 An entity is a concrete object (with a name) for which we need to
2159 allocate memory. Examples of entities are types and record
2160 objects.
2162 :attribute name: unqualified name of the entity
2163 :type: str
2165 """
2166 def __init__(self, name, location):
2167 # lobster-trace: LRM.Described_Name_Equality
2168 super().__init__(location)
2169 assert isinstance(name, str)
2170 self.name = name
2173class Typed_Entity(Entity, metaclass=ABCMeta):
2174 """Base class for entities with a type.
2176 A typed entity is a concrete object (with a name and TRLC type)
2177 for which we need to allocate memory. Examples of typed entities
2178 are record objects and components.
2180 :attribute n_typ: type of the entity
2181 :type: Type
2183 """
2184 def __init__(self, name, location, n_typ):
2185 # lobster-exclude: Constructor only declares variables
2186 super().__init__(name, location)
2187 assert isinstance(n_typ, Type)
2188 self.n_typ = n_typ
2191class Quantified_Variable(Typed_Entity):
2192 """Variable used in quantified expression.
2194 A quantified expression declares and binds a variable, for which
2195 we need a named entity. For example in::
2197 (forall x in array => x > 1)
2198 ^
2200 We represent this first x as a :class:`Quantified_Variable`, the
2201 second x will be an ordinary :class:`Name_Reference`.
2203 :attribute typ: type of the variable (i.e. element type of the array)
2204 :type: Type
2206 """
2207 def dump(self, indent=0): # pragma: no cover
2208 # lobster-exclude: Debugging feature
2209 self.write_indent(indent, "Quantified Variable %s" % self.name)
2210 self.n_typ.dump(indent + 1)
2213class Type(Entity, metaclass=ABCMeta):
2214 """Abstract base class for all types.
2216 """
2217 def perform_type_checks(self, mh, value):
2218 assert isinstance(mh, Message_Handler)
2219 assert isinstance(value, Expression)
2220 return True
2222 def get_example_value(self):
2223 # lobster-exclude: utility method
2224 assert False
2227class Concrete_Type(Type, metaclass=ABCMeta):
2228 # lobster-trace: LRM.Type_Declarations
2229 """Abstract base class for all non-anonymous types.
2231 :attribute n_package: package where this type was declared
2232 :type: Package
2233 """
2234 def __init__(self, name, location, n_package):
2235 super().__init__(name, location)
2236 assert isinstance(n_package, Package)
2237 self.n_package = n_package
2239 def fully_qualified_name(self):
2240 """Return the FQN for this type (i.e. PACKAGE.NAME)
2242 :returns: the type's full name
2243 :rtype: str
2244 """
2245 return self.n_package.name + "." + self.name
2247 def __hash__(self):
2248 return hash((self.n_package.name, self.name))
2250 def __repr__(self):
2251 return "%s<%s>" % (self.__class__.__name__,
2252 self.fully_qualified_name())
2255class Builtin_Type(Type, metaclass=ABCMeta):
2256 # lobster-trace: LRM.Builtin_Types
2257 """Abstract base class for all builtin types.
2259 """
2260 LOCATION = Location(file_name = "<builtin>")
2262 def __init__(self, name):
2263 super().__init__(name, Builtin_Type.LOCATION)
2265 def dump(self, indent=0): # pragma: no cover
2266 self.write_indent(indent, self.__class__.__name__)
2269class Builtin_Numeric_Type(Builtin_Type, metaclass=ABCMeta):
2270 # lobster-trace: LRM.Builtin_Types
2271 """Abstract base class for all builtin numeric types.
2273 """
2274 def dump(self, indent=0): # pragma: no cover
2275 self.write_indent(indent, self.__class__.__name__)
2278class Builtin_Function(Entity):
2279 # lobster-trace: LRM.Builtin_Functions
2280 """Builtin functions.
2282 These are auto-generated by the :class:`~trlc.trlc.Source_Manager`.
2284 :attribute arity: number of parameters
2285 :type: int
2287 :attribute arity_at_least: when true, arity indicates a lower bound
2288 :type: bool
2290 """
2291 LOCATION = Location(file_name = "<builtin>")
2293 def __init__(self, name, arity, arity_at_least=False):
2294 super().__init__(name, Builtin_Function.LOCATION)
2295 assert isinstance(arity, int)
2296 assert isinstance(arity_at_least, bool)
2297 assert arity >= 0
2298 self.arity = arity
2299 self.arity_at_least = arity_at_least
2301 def dump(self, indent=0): # pragma: no cover
2302 self.write_indent(indent, self.__class__.__name__ + " " + self.name)
2305class Array_Type(Type):
2306 """Anonymous array type.
2308 These are declared implicitly for each record component that has
2309 an array specifier::
2311 foo Integer [5 .. *]
2312 ^
2314 :attribute lower_bound: minimum number of elements
2315 :type: int
2317 :attribute loc_lower: text location of the lower bound indicator
2318 :type: Location
2320 :attribute upper_bound: maximum number of elements (or None)
2321 :type: int
2323 :attribute loc_upper: text location of the upper bound indicator
2324 :type: Location
2326 :attribute element_type: type of the array elements
2327 :type: Type
2329 """
2330 def __init__(self,
2331 location,
2332 element_type,
2333 loc_lower,
2334 lower_bound,
2335 loc_upper,
2336 upper_bound):
2337 # lobster-exclude: Constructor only declares variables
2338 assert isinstance(element_type, Type) or element_type is None
2339 assert isinstance(lower_bound, int)
2340 assert lower_bound >= 0
2341 assert upper_bound is None or isinstance(upper_bound, int)
2342 assert upper_bound is None or upper_bound >= 0
2343 assert isinstance(loc_lower, Location)
2344 assert isinstance(loc_upper, Location)
2346 if element_type is None: 2346 ↛ 2347line 2346 didn't jump to line 2347 because the condition on line 2346 was never true
2347 name = "universal array"
2348 elif upper_bound is None:
2349 if lower_bound == 0:
2350 name = "array of %s" % element_type.name
2351 else:
2352 name = "array of at least %u %s" % (lower_bound,
2353 element_type.name)
2354 elif lower_bound == upper_bound:
2355 name = "array of %u %s" % (lower_bound,
2356 element_type.name)
2357 else:
2358 name = "array of %u to %u %s" % (lower_bound,
2359 upper_bound,
2360 element_type.name)
2361 super().__init__(name, location)
2362 self.lower_bound = lower_bound
2363 self.loc_lower = loc_lower
2364 self.upper_bound = upper_bound
2365 self.loc_upper = loc_upper
2366 self.element_type = element_type
2368 def dump(self, indent=0): # pragma: no cover
2369 # lobster-exclude: Debugging feature
2370 self.write_indent(indent, "Array_Type")
2371 self.write_indent(indent + 1, "Lower bound: %u" % self.lower_bound)
2372 if self.upper_bound is None:
2373 self.write_indent(indent + 1, "Upper bound: *")
2374 else:
2375 self.write_indent(indent + 1, "Upper bound: %u" % self.upper_bound)
2376 self.write_indent(indent + 1, "Element type: %s" %
2377 self.element_type.name)
2379 def perform_type_checks(self, mh, value):
2380 assert isinstance(mh, Message_Handler)
2381 if isinstance(value, Array_Aggregate):
2382 return all(self.element_type.perform_type_checks(mh, v)
2383 for v in value.value)
2384 else:
2385 assert isinstance(value, Implicit_Null)
2386 return True
2388 def get_example_value(self):
2389 # lobster-exclude: utility method
2390 return "[%s]" % self.element_type.get_example_value()
2393class Builtin_Integer(Builtin_Numeric_Type):
2394 # lobster-trace: LRM.Builtin_Types
2395 # lobster-trace: LRM.Integer_Values
2396 """Builtin integer type."""
2397 def __init__(self):
2398 super().__init__("Integer")
2400 def get_example_value(self):
2401 # lobster-exclude: utility method
2402 return "100"
2405class Builtin_Decimal(Builtin_Numeric_Type):
2406 # lobster-trace: LRM.Builtin_Types
2407 # lobster-trace: LRM.Decimal_Values
2408 """Builtin decimal type."""
2409 def __init__(self):
2410 super().__init__("Decimal")
2412 def get_example_value(self):
2413 # lobster-exclude: utility method
2414 return "3.14"
2417class Builtin_Boolean(Builtin_Type):
2418 # lobster-trace: LRM.Builtin_Types
2419 # lobster-trace: LRM.Boolean_Values
2420 """Builtin boolean type."""
2421 def __init__(self):
2422 super().__init__("Boolean")
2424 def get_example_value(self):
2425 # lobster-exclude: utility method
2426 return "true"
2429class Builtin_String(Builtin_Type):
2430 # lobster-trace: LRM.Builtin_Types
2431 # lobster-trace: LRM.String_Values
2432 """Builtin string type."""
2433 def __init__(self):
2434 super().__init__("String")
2436 def get_example_value(self):
2437 # lobster-exclude: utility method
2438 return "\"potato\""
2441class Builtin_Markup_String(Builtin_String):
2442 # lobster-trace: LRM.Builtin_Types
2443 # lobster-trace: LRM.Markup_String_Values
2444 """Builtin string type that allows checked references to TRLC
2445 objects.
2446 """
2447 def __init__(self):
2448 super().__init__()
2449 self.name = "Markup_String"
2451 def get_example_value(self):
2452 # lobster-exclude: utility method
2453 return "\"also see [[potato]]\""
2456class Package(Entity):
2457 """Packages.
2459 A package is declared when it is first encountered (in either a
2460 rsl or trlc file). A package contains all symbols declared in it,
2461 both types and record objects. A package is not associated with
2462 just a single file, it can be spread over multiple files.
2464 :attribute declared_late: indicates if this package is declared in a \
2465 trlc file
2466 :type: bool
2468 :attribute symbols: symbol table of the package
2469 :type: Symbol_Table
2471 """
2472 def __init__(self, name, location, builtin_stab, declared_late):
2473 # lobster-exclude: Constructor only declares variables
2474 super().__init__(name, location)
2475 assert isinstance(builtin_stab, Symbol_Table)
2476 assert isinstance(declared_late, bool)
2477 self.symbols = Symbol_Table()
2478 self.symbols.make_visible(builtin_stab)
2479 self.declared_late = declared_late
2481 def dump(self, indent=0): # pragma: no cover
2482 # lobster-exclude: Debugging feature
2483 self.write_indent(indent, "Package %s" % self.name)
2484 self.write_indent(indent + 1, "Declared_Late: %s" % self.declared_late)
2485 self.symbols.dump(indent + 1, omit_heading=True)
2487 def __repr__(self):
2488 return "%s<%s>" % (self.__class__.__name__,
2489 self.name)
2492class Composite_Type(Concrete_Type, metaclass=ABCMeta):
2493 """Abstract base for record and tuple types, as they share some
2494 functionality.
2496 :attribute components: type components (including inherited if applicable)
2497 :type: Symbol_Table[Composite_Component]
2499 :attribute description: user-supplied description of the type or None
2500 :type: str
2502 :attribute checks: used-defined checks for this type (excluding \
2503 inherited checks)
2504 :type: list[Check]
2506 """
2507 def __init__(self,
2508 name,
2509 description,
2510 location,
2511 package,
2512 inherited_symbols=None):
2513 # lobster-trace: LRM.Described_Name_Description
2514 super().__init__(name, location, package)
2515 assert isinstance(description, str) or description is None
2516 assert isinstance(inherited_symbols, Symbol_Table) or \
2517 inherited_symbols is None
2519 self.components = Symbol_Table(inherited_symbols)
2520 self.description = description
2521 self.checks = []
2523 def add_check(self, n_check):
2524 # lobster-trace: LRM.Check_Evaluation_Order
2525 assert isinstance(n_check, Check)
2526 self.checks.append(n_check)
2528 def iter_checks(self):
2529 # lobster-trace: LRM.Check_Evaluation_Order
2530 yield from self.checks
2532 def all_components(self):
2533 # lobster-exclude: Convenience function
2534 """Convenience function to get a list of all components.
2536 :rtype: list[Composite_Component]
2537 """
2538 return list(self.components.table.values())
2541class Composite_Component(Typed_Entity):
2542 """Component in a record or tuple.
2544 When declaring a composite type, for each component an entity is
2545 declared::
2547 type|tuple T {
2548 foo "blah" optional Boolean
2549 ^1 ^2 ^3 ^4
2551 :attribute description: optional text (see 2) for this component, or None
2552 :type: str
2554 :attribute member_of: a link back to the containing record or tuple; \
2555 for inherited fields this refers back to the original base record type
2556 :type: Composite_Type
2558 :attribute optional: indicates if the component can be null or not (see 3)
2559 :type: bool
2561 """
2563 def __init__(self,
2564 name,
2565 description,
2566 location,
2567 member_of,
2568 n_typ,
2569 optional):
2570 # lobster-trace: LRM.Described_Name_Description
2571 super().__init__(name, location, n_typ)
2572 assert isinstance(description, str) or description is None
2573 assert isinstance(member_of, Composite_Type)
2574 assert isinstance(optional, bool)
2575 self.description = description
2576 self.member_of = member_of
2577 self.optional = optional
2579 def dump(self, indent=0): # pragma: no cover
2580 # lobster-exclude: Debugging feature
2581 self.write_indent(indent, "Composite_Component %s" % self.name)
2582 if self.description:
2583 self.write_indent(indent + 1, "Description: %s" % self.description)
2584 self.write_indent(indent + 1, "Optional: %s" % self.optional)
2585 self.write_indent(indent + 1, "Type: %s" % self.n_typ.name)
2587 def __repr__(self):
2588 return "%s<%s>" % (self.__class__.__name__,
2589 self.member_of.fully_qualified_name() + "." +
2590 self.name)
2593class Record_Type(Composite_Type):
2594 """A user-defined record type.
2596 In this example::
2598 type T "optional description of T" extends Root_T {
2599 ^1 ^2 ^3
2601 Note that (1) is part of the :class:`Entity` base, and (2) is part
2602 of the :class:`Composite_Type` base.
2604 :attribute parent: root type or None, indicated by (3) above
2605 :type: Record_Type
2607 :attribute frozen: mapping of frozen components
2608 :type: dict[str, Expression]
2610 :attribute is_final: type is final (i.e. no new components may be declared)
2611 :type: bool
2613 :attribute is_abstract: type is abstract
2614 :type: bool
2616 """
2617 def __init__(self,
2618 name,
2619 description,
2620 location,
2621 package,
2622 n_parent,
2623 is_abstract):
2624 # lobster-exclude: Constructor only declares variables
2625 assert isinstance(n_parent, Record_Type) or n_parent is None
2626 assert isinstance(is_abstract, bool)
2627 super().__init__(name,
2628 description,
2629 location,
2630 package,
2631 n_parent.components if n_parent else None)
2632 self.parent = n_parent
2633 self.frozen = {}
2634 self.is_final = (n_parent.is_final if n_parent else False)
2635 self.is_abstract = is_abstract
2637 def iter_checks(self):
2638 # lobster-trace: LRM.Check_Evaluation_Order
2639 # lobster-trace: LRM.Check_Evaluation_Order_For_Extensions
2640 if self.parent:
2641 yield from self.parent.iter_checks()
2642 yield from self.checks
2644 def dump(self, indent=0): # pragma: no cover
2645 # lobster-exclude: Debugging feature
2646 self.write_indent(indent, "Record_Type %s" % self.name)
2647 if self.description:
2648 self.write_indent(indent + 1, "Description: %s" % self.description)
2649 if self.parent:
2650 self.write_indent(indent + 1, "Parent: %s" % self.parent.name)
2651 self.components.dump(indent + 1, omit_heading=True)
2652 if self.checks:
2653 self.write_indent(indent + 1, "Checks")
2654 for n_check in self.checks:
2655 n_check.dump(indent + 2)
2656 else:
2657 self.write_indent(indent + 1, "Checks: None")
2659 def all_components(self):
2660 """Convenience function to get a list of all components.
2662 :rtype: list[Composite_Component]
2663 """
2664 if self.parent:
2665 return self.parent.all_components() + \
2666 list(self.components.table.values())
2667 else:
2668 return list(self.components.table.values())
2670 def is_subclass_of(self, record_type):
2671 """ Checks if this record type is or inherits from the given type
2673 :param record_type: check if are or extend this type
2674 :type record_type: Record_Type
2676 :returns: true if we are or extend the given type
2677 :rtype: Boolean
2678 """
2679 assert isinstance(record_type, Record_Type)
2681 ptr = self
2682 while ptr:
2683 if ptr is record_type:
2684 return True
2685 else:
2686 ptr = ptr.parent
2687 return False
2689 def is_frozen(self, n_component):
2690 """Test if the given component is frozen.
2692 :param n_component: a composite component of this record type \
2693 (or any of its parents)
2694 :type n_component: Composite_Component
2696 :rtype: bool
2697 """
2698 assert isinstance(n_component, Composite_Component)
2699 if n_component.name in self.frozen:
2700 return True
2701 elif self.parent:
2702 return self.parent.is_frozen(n_component)
2703 else:
2704 return False
2706 def get_freezing_expression(self, n_component):
2707 """Retrieve the frozen value for a frozen component
2709 It is an internal compiler error to call this method with a
2710 component that his not frozen.
2712 :param n_component: a frozen component of this record type \
2713 (or any of its parents)
2714 :type n_component: Composite_Component
2716 :rtype: Expression
2718 """
2719 assert isinstance(n_component, Composite_Component)
2720 if n_component.name in self.frozen: 2720 ↛ 2722line 2720 didn't jump to line 2722 because the condition on line 2720 was always true
2721 return self.frozen[n_component.name]
2722 elif self.parent:
2723 return self.parent.get_freezing_expression(n_component)
2724 else:
2725 assert False
2727 def get_example_value(self):
2728 # lobster-exclude: utility method
2729 return "%s_instance" % self.name
2732class Tuple_Type(Composite_Type):
2733 """A user-defined tuple type.
2735 In this example::
2737 tuple T "optional description of T" {
2738 ^1 ^2
2740 Note that (1) is part of the :class:`Entity` base, and (2) is part
2741 of the :class:`Composite_Type` base.
2743 :attribute separators: list of syntactic separators.
2744 :type: list[Separator]
2746 Note the list of separators will either be empty, or there will be
2747 precisely one less separator than components.
2749 """
2750 def __init__(self, name, description, location, package):
2751 # lobster-trace: LRM.Tuple_Declaration
2752 super().__init__(name,
2753 description,
2754 location,
2755 package)
2756 self.separators = []
2758 def add_separator(self, n_separator):
2759 # lobster-exclude: utility method
2760 assert isinstance(n_separator, Separator)
2761 assert len(self.separators) + 1 == len(self.components.table)
2762 self.separators.append(n_separator)
2764 def iter_separators(self):
2765 """Iterate over all separators"""
2766 # lobster-exclude: utility method
2767 yield from self.separators
2769 def iter_sequence(self):
2770 """Iterate over all components and separators in syntactic order"""
2771 # lobster-exclude: utility method
2772 if self.separators:
2773 for i, n_component in enumerate(self.components.table.values()):
2774 yield n_component
2775 if i < len(self.separators):
2776 yield self.separators[i]
2777 else:
2778 yield from self.components.table.values()
2780 def has_separators(self):
2781 """Returns true if a tuple type requires separators"""
2782 # lobster-exclude: utility method
2783 return bool(self.separators)
2785 def dump(self, indent=0): # pragma: no cover
2786 # lobster-exclude: Debugging feature
2787 self.write_indent(indent, "Tuple_Type %s" % self.name)
2788 if self.description:
2789 self.write_indent(indent + 1, "Description: %s" % self.description)
2790 self.write_indent(indent + 1, "Fields")
2791 for n_item in self.iter_sequence():
2792 n_item.dump(indent + 2)
2793 if self.checks:
2794 self.write_indent(indent + 1, "Checks")
2795 for n_check in self.checks:
2796 n_check.dump(indent + 2)
2797 else:
2798 self.write_indent(indent + 1, "Checks: None")
2800 def perform_type_checks(self, mh, value):
2801 # lobster-trace: LRM.Check_Evaluation_Order
2802 assert isinstance(mh, Message_Handler)
2803 if isinstance(value, Tuple_Aggregate): 2803 ↛ 2810line 2803 didn't jump to line 2810 because the condition on line 2803 was always true
2804 ok = True
2805 for check in self.iter_checks():
2806 if not check.perform(mh, value): 2806 ↛ 2807line 2806 didn't jump to line 2807 because the condition on line 2806 was never true
2807 ok = False
2808 return ok
2809 else:
2810 assert isinstance(value, Implicit_Null)
2811 return True
2813 def get_example_value(self):
2814 # lobster-exclude: utility method
2815 parts = []
2816 for n_item in self.iter_sequence():
2817 if isinstance(n_item, Composite_Component):
2818 parts.append(n_item.n_typ.get_example_value())
2819 else:
2820 parts.append(n_item.to_string())
2821 if self.has_separators():
2822 return " ".join(parts)
2823 else:
2824 return "(%s)" % ", ".join(parts)
2827class Separator(Node):
2828 # lobster-trace: LRM.Tuple_Declaration
2829 """User-defined syntactic separator
2831 For example::
2833 separator x
2834 ^1
2836 :attribute token: token used to separate fields of the tuple
2837 :type: Token
2838 """
2839 def __init__(self, token):
2840 super().__init__(token.location)
2841 assert isinstance(token, Token) and token.kind in ("IDENTIFIER",
2842 "AT",
2843 "COLON",
2844 "SEMICOLON")
2845 self.token = token
2847 def to_string(self):
2848 return {
2849 "AT" : "@",
2850 "COLON" : ":",
2851 "SEMICOLON" : ";"
2852 }.get(self.token.kind, self.token.value)
2854 def dump(self, indent=0): # pragma: no cover
2855 self.write_indent(indent, "Separator %s" % self.token.value)
2858class Enumeration_Type(Concrete_Type):
2859 """User-defined enumeration types.
2861 For example::
2863 enum T "potato" {
2864 ^1 ^2
2866 :attribute description: user supplied optional description, or None
2867 :type: str
2869 :attribute literals: the literals in this enumeration
2870 :type: Symbol_Table[Enumeration_Literal_Spec]
2872 """
2873 def __init__(self, name, description, location, package):
2874 # lobster-trace: LRM.Described_Name_Description
2875 super().__init__(name, location, package)
2876 assert isinstance(description, str) or description is None
2877 self.literals = Symbol_Table()
2878 self.description = description
2880 def dump(self, indent=0): # pragma: no cover
2881 # lobster-exclude: Debugging feature
2882 self.write_indent(indent, "Enumeration_Type %s" % self.name)
2883 if self.description:
2884 self.write_indent(indent + 1, "Description: %s" % self.description)
2885 self.literals.dump(indent + 1, omit_heading=True)
2887 def get_example_value(self):
2888 # lobster-exclude: utility method
2889 options = list(self.literals.values())
2890 if options:
2891 choice = len(options) // 2
2892 return self.name + "." + choice.name
2893 else:
2894 return "ERROR"
2897class Enumeration_Literal_Spec(Typed_Entity):
2898 """Declared literal in an enumeration declaration.
2900 Note that for literals mentioned later in record object
2901 declarations, we use :class:`Enumeration_Literal`. Literal specs
2902 are used here::
2904 enum ASIL {
2905 QM "not safety related"
2906 ^1 ^2
2908 :attribute description: the optional user-supplied description, or None
2909 :type: str
2911 """
2912 def __init__(self, name, description, location, enum):
2913 # lobster-trace: LRM.Described_Name_Description
2914 super().__init__(name, location, enum)
2915 assert isinstance(description, str) or description is None
2916 assert isinstance(enum, Enumeration_Type)
2917 self.description = description
2919 def dump(self, indent=0): # pragma: no cover
2920 # lobster-exclude: Debugging feature
2921 self.write_indent(indent, "Enumeration_Literal_Spec %s" % self.name)
2922 if self.description:
2923 self.write_indent(indent + 1, "Description: %s" % self.description)
2926class Record_Object(Typed_Entity):
2927 """A declared instance of a record type.
2929 This is going to be the bulk of all entities created by TRLC::
2931 section "Potato" {
2932 ^5
2933 Requirement PotatoReq {
2934 ^1 ^2
2935 component1 = 42
2936 ^3 ^4
2938 Note that the name (see 2) and type (see 1) of the object is
2939 provided by the name attribute of the :class:`Typed_Entity` base
2940 class.
2942 :attribute field: the specific values for all components (see 3 and 4)
2943 :type: dict[str, Expression]
2945 :attribute section: None or the section this record is contained in (see 5)
2946 :type: Section
2948 :attribute n_package: The package in which this record is declared in
2949 :type: Section
2951 The actual type of expressions in the field attribute are limited
2952 to:
2954 * :class:`Literal`
2955 * :class:`Unary_Expression`
2956 * :class:`Array_Aggregate`
2957 * :class:`Tuple_Aggregate`
2958 * :class:`Record_Reference`
2959 * :class:`Implicit_Null`
2961 """
2962 def __init__(self, name, location, n_typ, section, n_package):
2963 # lobster-trace: LRM.Section_Declaration
2964 # lobster-trace: LRM.Unspecified_Optional_Components
2965 # lobster-trace: LRM.Record_Object_Declaration
2967 assert isinstance(n_typ, Record_Type)
2968 assert isinstance(section, list) or section is None
2969 assert isinstance(n_package, Package)
2970 super().__init__(name, location, n_typ)
2971 self.field = {
2972 comp.name: Implicit_Null(self, comp)
2973 for comp in self.n_typ.all_components()
2974 }
2975 self.section = section
2976 self.n_package = n_package
2978 def fully_qualified_name(self):
2979 """Return the FQN for this type (i.e. PACKAGE.NAME)
2981 :returns: the object's full name
2982 :rtype: str
2983 """
2984 return self.n_package.name + "." + self.name
2986 def to_python_dict(self):
2987 """Return an evaluated and simplified object for Python.
2989 For example it might provide::
2991 {"foo" : [1, 2, 3],
2992 "bar" : None,
2993 "baz" : "value"}
2995 This is a function especially designed for the Python API. The
2996 name of the object itself is not in this returned dictionary.
2998 """
2999 return {name: value.to_python_object()
3000 for name, value in self.field.items()}
3002 def is_component_implicit_null(self, component) -> bool:
3003 return not isinstance(self.field[component.name], Implicit_Null)
3005 def assign(self, component, value):
3006 assert isinstance(component, Composite_Component)
3007 assert isinstance(value, (Literal,
3008 Array_Aggregate,
3009 Tuple_Aggregate,
3010 Record_Reference,
3011 Implicit_Null,
3012 Unary_Expression)), \
3013 "value is %s" % value.__class__.__name__
3014 if self.is_component_implicit_null(component): 3014 ↛ 3015line 3014 didn't jump to line 3015 because the condition on line 3014 was never true
3015 raise KeyError(f"Component {component.name} already \
3016 assigned to {self.n_typ.name} {self.name}!")
3017 self.field[component.name] = value
3019 def dump(self, indent=0): # pragma: no cover
3020 # lobster-exclude: Debugging feature
3021 self.write_indent(indent, "Record_Object %s" % self.name)
3022 self.write_indent(indent + 1, "Type: %s" % self.n_typ.name)
3023 for key, value in self.field.items():
3024 self.write_indent(indent + 1, "Field %s" % key)
3025 value.dump(indent + 2)
3026 if self.section:
3027 self.section[-1].dump(indent + 1)
3029 def resolve_references(self, mh):
3030 assert isinstance(mh, Message_Handler)
3031 for val in self.field.values():
3032 val.resolve_references(mh)
3034 def perform_checks(self, mh):
3035 # lobster-trace: LRM.Check_Evaluation_Order
3036 # lobster-trace: LRM.Evaluation_Of_Checks
3037 assert isinstance(mh, Message_Handler)
3039 ok = True
3041 # First evaluate all tuple checks
3042 for n_comp in self.n_typ.all_components():
3043 if not n_comp.n_typ.perform_type_checks(mh, 3043 ↛ 3045line 3043 didn't jump to line 3045 because the condition on line 3043 was never true
3044 self.field[n_comp.name]):
3045 ok = False
3047 # Then evaluate all record checks
3048 for check in self.n_typ.iter_checks():
3049 # Prints messages, if applicable. Raises exception on
3050 # fatal checks, which causes this to abort.
3051 if not check.perform(mh, self):
3052 ok = False
3054 return ok
3056 def __repr__(self):
3057 return "%s<%s>" % (self.__class__.__name__,
3058 self.n_package.name + "." +
3059 self.n_typ.name + "." +
3060 self.name)
3063class Section(Entity):
3064 # lobster-trace: LRM.Section_Declaration
3065 """A section for readability
3067 This represents a section construct in TRLC files to group record
3068 objects together::
3070 section "Foo" {
3071 ^^^^^ parent section
3072 section "Bar" {
3073 ^^^^^ section
3075 :attribute parent: the parent section or None
3076 :type: Section
3078 """
3079 def __init__(self, name, location, parent):
3080 super().__init__(name, location)
3081 assert isinstance(parent, Section) or parent is None
3082 self.parent = parent
3084 def dump(self, indent=0): # pragma: no cover
3085 self.write_indent(indent, "Section %s" % self.name)
3086 if self.parent is None:
3087 self.write_indent(indent + 1, "Parent: None")
3088 else:
3089 self.write_indent(indent + 1, "Parent: %s" % self.parent.name)
3092##############################################################################
3093# Symbol Table & Scopes
3094##############################################################################
3096class Symbol_Table:
3097 """ Symbol table mapping names to entities
3098 """
3099 def __init__(self, parent=None):
3100 # lobster-exclude: Constructor only declares variables
3101 assert isinstance(parent, Symbol_Table) or parent is None
3102 self.parent = parent
3103 self.imported = []
3104 self.table = OrderedDict()
3105 self.trlc_files = []
3106 self.section_names = []
3108 @staticmethod
3109 def simplified_name(name):
3110 # lobster-trace: LRM.Sufficiently_Distinct
3111 assert isinstance(name, str)
3112 return name.lower().replace("_", "")
3114 def all_names(self):
3115 # lobster-exclude: API for users
3116 """ All names in the symbol table
3118 :rtype: set[str]
3119 """
3120 rv = set(item.name for item in self.table.values())
3121 if self.parent:
3122 rv |= self.parent.all_names()
3123 return rv
3125 def iter_record_objects_by_section(self):
3126 """API for users
3128 Retriving information about the section hierarchy for record objects
3129 Inputs: folder with trlc files where trlc files have sections,
3130 sub sections and record objects
3131 Output: Information about sections and level of sections,
3132 record objects and levels of record object
3133 """
3134 for record_object in self.iter_record_objects():
3135 location = record_object.location.file_name
3136 if location not in self.trlc_files: 3136 ↛ 3139line 3136 didn't jump to line 3139 because the condition on line 3136 was always true
3137 self.trlc_files.append(location)
3138 yield location
3139 if record_object.section:
3140 object_level = len(record_object.section) - 1
3141 for level, section in enumerate(record_object.section):
3142 if section not in self.section_names: 3142 ↛ 3141line 3142 didn't jump to line 3141 because the condition on line 3142 was always true
3143 self.section_names.append(section)
3144 yield section.name, level
3145 yield record_object, object_level
3146 else:
3147 object_level = 0
3148 yield record_object, object_level
3150 def iter_record_objects(self):
3151 # lobster-exclude: API for users
3152 """ Iterate over all record objects
3154 :rtype: iterable[Record_Object]
3155 """
3156 for item in self.table.values():
3157 if isinstance(item, Package):
3158 yield from item.symbols.iter_record_objects()
3160 elif isinstance(item, Record_Object):
3161 yield item
3163 def values(self, subtype=None):
3164 # lobster-exclude: API for users
3165 assert subtype is None or isinstance(subtype, type)
3166 if self.parent:
3167 yield from self.parent.values(subtype)
3168 for name in sorted(self.table):
3169 if subtype is None or isinstance(self.table[name], subtype):
3170 yield self.table[name]
3172 def make_visible(self, stab):
3173 assert isinstance(stab, Symbol_Table)
3174 self.imported.append(stab)
3176 def register(self, mh, entity):
3177 # lobster-trace: LRM.Duplicate_Types
3178 # lobster-trace: LRM.Unique_Enumeration_Literals
3179 # lobster-trace: LRM.Tuple_Unique_Field_Names
3180 # lobster-trace: LRM.Sufficiently_Distinct
3181 # lobster-trace: LRM.Unique_Object_Names
3183 assert isinstance(mh, Message_Handler)
3184 assert isinstance(entity, Entity)
3186 simple_name = self.simplified_name(entity.name)
3188 if self.contains_raw(simple_name):
3189 pdef = self.lookup_direct(mh, entity.name, entity.location,
3190 simplified=True)
3191 if pdef.name == entity.name:
3192 mh.error(entity.location,
3193 "duplicate definition, previous definition at %s" %
3194 mh.cross_file_reference(pdef.location))
3195 else:
3196 mh.error(entity.location,
3197 "%s is too similar to %s, declared at %s" %
3198 (entity.name,
3199 pdef.name,
3200 mh.cross_file_reference(pdef.location)))
3202 else:
3203 self.table[simple_name] = entity
3205 def __contains__(self, name):
3206 # lobster-trace: LRM.Described_Name_Equality
3207 return self.contains(name)
3209 def contains_raw(self, simple_name, precise_name=None):
3210 # lobster-trace: LRM.Described_Name_Equality
3211 # lobster-trace: LRM.Sufficiently_Distinct
3212 #
3213 # Internal function to test if the simplified name is in the
3214 # table.
3215 assert isinstance(simple_name, str)
3216 assert isinstance(precise_name, str) or precise_name is None
3218 if simple_name in self.table:
3219 # No need to continue searching since registering a
3220 # clashing name would have been stopped
3221 return precise_name is None or \
3222 self.table[simple_name].name == precise_name
3224 elif self.parent:
3225 return self.parent.contains_raw(simple_name, precise_name)
3227 for stab in self.imported:
3228 if stab.contains_raw(simple_name, precise_name):
3229 return True
3231 return False
3233 def contains(self, name):
3234 # lobster-trace: LRM.Described_Name_Equality
3235 """ Tests if the given name is in the table
3237 :param name: the name to test
3238 :type name: str
3240 :rtype: bool
3241 """
3242 assert isinstance(name, str)
3243 return self.contains_raw(self.simplified_name(name), name)
3245 def lookup_assuming(self, mh, name, required_subclass=None):
3246 # lobster-trace: LRM.Described_Name_Equality
3247 # lobster-trace: LRM.Sufficiently_Distinct
3248 """Retrieve an object from the table assuming its there
3250 This is intended for the API specifically where you want to
3251 e.g. find some used-defined types you know are there.
3253 :param mh: The message handler to use
3254 :type mh: Message_Handler
3256 :param name: The name to search for
3257 :type name: str
3259 :param required_subclass: If set, creates an error if the object \
3260 is not an instance of the given class
3261 :type required_subclass: type
3263 :raise TRLC_Error: if the object is not of the required subclass
3264 :returns: the specified entity (or None if it does not exist)
3265 :rtype: Entity
3267 """
3268 assert isinstance(mh, Message_Handler)
3269 assert isinstance(name, str)
3270 assert isinstance(required_subclass, type) or required_subclass is None
3272 simple_name = self.simplified_name(name)
3274 ptr = self
3275 for ptr in [self] + self.imported: 3275 ↛ 3293line 3275 didn't jump to line 3293 because the loop on line 3275 didn't complete
3276 while ptr: 3276 ↛ 3275line 3276 didn't jump to line 3275 because the condition on line 3276 was always true
3277 if simple_name in ptr.table: 3277 ↛ 3291line 3277 didn't jump to line 3291 because the condition on line 3277 was always true
3278 rv = ptr.table[simple_name]
3279 if rv.name != name: 3279 ↛ 3280line 3279 didn't jump to line 3280 because the condition on line 3279 was never true
3280 return None
3282 if required_subclass is not None and \ 3282 ↛ 3284line 3282 didn't jump to line 3284 because the condition on line 3282 was never true
3283 not isinstance(rv, required_subclass):
3284 mh.error(rv.location,
3285 "%s %s is not a %s" %
3286 (rv.__class__.__name__,
3287 name,
3288 required_subclass.__name__))
3289 return rv
3290 else:
3291 ptr = ptr.parent
3293 return None
3295 def lookup_direct(self,
3296 mh,
3297 name,
3298 error_location,
3299 required_subclass=None,
3300 simplified=False):
3301 # lobster-trace: LRM.Described_Name_Equality
3302 # lobster-trace: LRM.Sufficiently_Distinct
3303 # lobster-trace: LRM.Valid_Base_Names
3304 # lobster-trace: LRM.Valid_Access_Prefixes
3305 # lobster-trace: LRM.Valid_Function_Prefixes
3306 """Retrieve an object from the table
3308 For example::
3310 pkg = stab.lookup_direct(mh,
3311 "potato",
3312 Location("foobar.txt", 42),
3313 Package)
3315 This would search for an object named ``potato``. If it is
3316 found, and it is a package, it is returned. If it is not a
3317 Package, then the following error is issued::
3319 foobar.txt:42: error: Enumeration_Type potato is not a Package
3321 If it is not found at all, then the following error is issued::
3323 foobar.txt:42: error: unknown symbol potato
3325 :param mh: The message handler to use
3326 :type mh: Message_Handler
3328 :param name: The name to search for
3329 :type name: str
3331 :param error_location: Where to create the error if the name is \
3332 not found
3333 :type error_location: Location
3335 :param required_subclass: If set, creates an error if the object \
3336 is not an instance of the given class
3337 :type required_subclass: type
3339 :param simplified: If set, look up the given simplified name instead \
3340 of the actual name
3341 :type simplified: bool
3343 :raise TRLC_Error: if the name is not in the table
3344 :raise TRLC_Error: if the object is not of the required subclass
3345 :returns: the specified entity
3346 :rtype: Entity
3348 """
3349 assert isinstance(mh, Message_Handler)
3350 assert isinstance(name, str)
3351 assert isinstance(error_location, Location)
3352 assert isinstance(required_subclass, type) or required_subclass is None
3353 assert isinstance(simplified, bool)
3355 simple_name = self.simplified_name(name)
3356 ptr = self
3357 options = []
3359 for ptr in [self] + self.imported:
3360 while ptr:
3361 if simple_name in ptr.table:
3362 rv = ptr.table[simple_name]
3363 if not simplified and rv.name != name: 3363 ↛ 3364line 3363 didn't jump to line 3364 because the condition on line 3363 was never true
3364 mh.error(error_location,
3365 "unknown symbol %s, did you mean %s?" %
3366 (name,
3367 rv.name))
3369 if required_subclass is not None and \
3370 not isinstance(rv, required_subclass):
3371 mh.error(error_location,
3372 "%s %s is not a %s" %
3373 (rv.__class__.__name__,
3374 name,
3375 required_subclass.__name__))
3376 return rv
3377 else:
3378 options += list(item.name
3379 for item in ptr.table.values())
3380 ptr = ptr.parent
3382 matches = get_close_matches(
3383 word = name,
3384 possibilities = options,
3385 n = 1)
3387 if matches:
3388 mh.error(error_location,
3389 "unknown symbol %s, did you mean %s?" %
3390 (name,
3391 matches[0]))
3392 else:
3393 mh.error(error_location,
3394 "unknown symbol %s" % name)
3396 def lookup(self, mh, referencing_token, required_subclass=None):
3397 # lobster-trace: LRM.Described_Name_Equality
3398 assert isinstance(mh, Message_Handler)
3399 assert isinstance(referencing_token, Token)
3400 assert referencing_token.kind in ("IDENTIFIER", "BUILTIN")
3401 assert isinstance(required_subclass, type) or required_subclass is None
3403 return self.lookup_direct(
3404 mh = mh,
3405 name = referencing_token.value,
3406 error_location = referencing_token.location,
3407 required_subclass = required_subclass)
3409 def write_indent(self, indent, message): # pragma: no cover
3410 # lobster-exclude: Debugging feature
3411 assert isinstance(indent, int)
3412 assert indent >= 0
3413 assert isinstance(message, str)
3414 print(" " * (3 * indent) + message)
3416 def dump(self, indent=0, omit_heading=False): # pragma: no cover
3417 # lobster-exclude: Debugging feature
3418 if omit_heading:
3419 new_indent = indent
3420 else:
3421 self.write_indent(indent, "Symbol_Table")
3422 new_indent = indent + 1
3423 ptr = self
3424 while ptr:
3425 for name in ptr.table:
3426 ptr.table[name].dump(new_indent)
3427 ptr = ptr.parent
3429 @classmethod
3430 def create_global_table(cls, mh):
3431 # lobster-trace: LRM.Builtin_Types
3432 # lobster-trace: LRM.Builtin_Functions
3433 # lobster-trace: LRM.Builtin_Type_Conversion_Functions
3434 # lobster-trace: LRM.Signature_Len
3435 # lobster-trace: LRM.Signature_String_End_Functions
3436 # lobster-trace: LRM.Signature_Matches
3438 stab = Symbol_Table()
3439 stab.register(mh, Builtin_Integer())
3440 stab.register(mh, Builtin_Decimal())
3441 stab.register(mh, Builtin_Boolean())
3442 stab.register(mh, Builtin_String())
3443 stab.register(mh, Builtin_Markup_String())
3444 stab.register(mh,
3445 Builtin_Function("len", 1))
3446 stab.register(mh,
3447 Builtin_Function("startswith", 2))
3448 stab.register(mh,
3449 Builtin_Function("endswith", 2))
3450 stab.register(mh,
3451 Builtin_Function("matches", 2))
3452 stab.register(mh,
3453 Builtin_Function("oneof", 1, arity_at_least=True))
3455 return stab
3458class Scope:
3459 def __init__(self):
3460 # lobster-exclude: Constructor only declares variables
3461 self.scope = []
3463 def push(self, stab):
3464 assert isinstance(stab, Symbol_Table)
3465 self.scope.append(stab)
3467 def pop(self):
3468 self.scope.pop()
3470 def contains(self, name):
3471 assert isinstance(name, str)
3473 for stab in reversed(self.scope):
3474 if stab.contains(name):
3475 return True
3476 return False
3478 def lookup(self, mh, referencing_token, required_subclass=None):
3479 assert len(self.scope) >= 1
3480 assert isinstance(mh, Message_Handler)
3481 assert isinstance(referencing_token, Token)
3482 assert referencing_token.kind in ("IDENTIFIER", "BUILTIN")
3483 assert isinstance(required_subclass, type) or required_subclass is None
3485 for stab in reversed(self.scope[1:]):
3486 if stab.contains(referencing_token.value):
3487 return stab.lookup(mh, referencing_token, required_subclass)
3488 return self.scope[0].lookup(mh, referencing_token, required_subclass)
3490 def size(self):
3491 return len(self.scope)