Coverage for trlc/ast.py: 90%
1317 statements
« prev ^ index » next coverage.py v7.10.7, created at 2026-06-18 05:43 +0000
« prev ^ index » next coverage.py v7.10.7, created at 2026-06-18 05:43 +0000
1#!/usr/bin/env python3
2#
3# TRLC - Treat Requirements Like Code
4# Copyright (C) 2022-2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
5# Copyright (C) 2024-2025 Florian Schanda
6#
7# This file is part of the TRLC Python Reference Implementation.
8#
9# TRLC is free software: you can redistribute it and/or modify it
10# under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# TRLC is distributed in the hope that it will be useful, but WITHOUT
15# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
17# License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with TRLC. If not, see <https://www.gnu.org/licenses/>.
22from abc import ABCMeta, abstractmethod
23import re
25from copy import copy
26from difflib import get_close_matches
27from enum import Enum, auto
28from collections import OrderedDict
29from fractions import Fraction
31from trlc.errors import TRLC_Error, Location, Message_Handler
32from trlc.lexer import Token
33from trlc import math
35#
36# This module defines the AST and related object for the TRLC
37# reference implementation. There are four sections:
38#
39# - Valuations deal with concrete values for record objects
40# - AST expressions deal with the syntax tree
41# - AST entities deal with concrete objects that have been declared
42# - Symbol_Table and scope deals with name resolution
43#
46##############################################################################
47# Valuations
48##############################################################################
50class Value:
51 # lobster-trace: LRM.Boolean_Values
52 # lobster-trace: LRM.Integer_Values
53 # lobster-trace: LRM.Decimal_Values
54 # lobster-trace: LRM.String_Values
55 # lobster-trace: LRM.Markup_String_Values
56 """Polymorphic value for evaluating expressions.
58 Any record references will be fully resolved.
60 :attribute location: source location this value comes from
61 :type: Location
63 :attribute value: the value or None (for null values)
64 :type: str, int, bool, fractions.Fraction, list[Value], \
65 Record_Reference, Enumeration_Literal_Spec
67 :attribute typ: type of the value (or None for null values)
68 :type: Type
69 """
70 def __init__(self, location, value, typ):
71 assert isinstance(location, Location)
72 assert value is None or \
73 isinstance(value, (str,
74 int,
75 bool,
76 list, # for arrays
77 dict, # for tuples
78 Fraction,
79 Record_Reference,
80 Enumeration_Literal_Spec))
81 assert typ is None or isinstance(typ, Type)
82 assert (typ is None) == (value is None)
84 self.location = location
85 self.value = value
86 self.typ = typ
88 def __eq__(self, other):
89 return self.typ == other.typ and self.value == other.value
91 def __repr__(self): # pragma: no cover
92 return "Value(%s)" % self.value
94 def resolve_references(self, mh):
95 assert isinstance(mh, Message_Handler)
97 if isinstance(self.value, Record_Reference):
98 self.value.resolve(mh)
101##############################################################################
102# AST Nodes
103##############################################################################
105class Node(metaclass=ABCMeta):
106 """Base class for all AST items.
108 :attribute location: source location
109 :type: Location
110 """
111 def __init__(self, location):
112 # lobster-exclude: Constructor only declares variables
113 assert isinstance(location, Location)
114 self.location = location
116 def set_ast_link(self, tok):
117 assert isinstance(tok, Token)
118 tok.ast_link = self
120 def write_indent(self, indent, message): # pragma: no cover
121 # lobster-exclude: Debugging feature
122 assert isinstance(indent, int)
123 assert indent >= 0
124 assert isinstance(message, str)
125 print(" " * (3 * indent) + message)
127 @abstractmethod
128 def dump(self, indent=0): # pragma: no cover
129 """Visualise the parse tree.
131 This can be called for any :class:`Node` or
132 :class:`Symbol_Table`, and can be very helpful for debugging
133 or understanding the parse tree. The dump method will produce
134 output like this::
136 Symbol_Table
137 Builtin_Boolean
138 Builtin_Integer
139 Builtin_Decimal
140 Builtin_String
141 Builtin_Markup_String
142 Package bar
143 Symbol_Table
144 Record_Type MyType
145 Composite_Component name
146 Optional: False
147 Type: String
148 Checks
149 Error 'description is too short'
150 Anchor: description
151 Binary Binary_Operator.COMP_GT Expression
152 Type: Boolean
153 Unary Unary_Operator.STRING_LENGTH Expression
154 Type: Integer
155 Name Reference to description
156 Integer Literal 10
157 Package instances
158 Symbol_Table
159 Record_Object SomeThing
160 Type: MyType
161 Field description: "Potato"
162 Builtin_Function endswith
163 Builtin_Function len
164 Builtin_Function matches
165 Builtin_Function startswith
166 Builtin_Function oneof
168 """
169 assert isinstance(indent, int) and indent >= 0
170 assert False, f"dump not implemented for {self.__class__.__name__}"
171 # lobster-exclude: Debugging feature
174class Check_Block(Node):
175 """Node representing check blocks
177 Semantically this has no meaning, but it's nice to have some kind
178 of similar representation to how it's in the file.
180 :attribute n_typ: composite type for which the checks apply
181 :type: Composite_Type
183 :attribute checks: list of checks
184 :type: list[Check]
186 """
187 def __init__(self, location, n_typ):
188 # lobster-trace: LRM.Check_Block
189 super().__init__(location)
190 assert isinstance(n_typ, Composite_Type)
191 self.n_typ = n_typ
192 self.checks = []
194 def add_check(self, n_check):
195 # lobster-trace: LRM.Check_Evaluation_Order
196 assert isinstance(n_check, Check)
197 self.checks.append(n_check)
199 def dump(self, indent=0): # pragma: no cover
200 # lobster-exclude: Debugging feature
201 self.write_indent(indent, "Check_Block")
202 self.write_indent(indent + 1, f"Type: {self.n_typ.name}")
203 for n_check in self.checks:
204 n_check.dump(indent + 1)
207class Compilation_Unit(Node):
208 """Special node to represent the concrete file structure
210 :attribute package: the main package this file declares or contributes to
211 :type: Package
213 :attribute imports: package imported by this file
214 :type: list[Package]
216 :attribute items: list of
217 :type: list[Node]
219 """
220 def __init__(self, file_name):
221 # lobster-exclude: Constructor only declares variables
222 super().__init__(Location(file_name))
223 self.package = None
224 self.imports = None
225 self.raw_imports = []
226 self.items = []
228 def dump(self, indent=0): # pragma: no cover
229 # lobster-exclude: Debugging feature
230 self.write_indent(indent, f"Compilation_Unit ({self.location.file_name})")
231 for t_import in self.raw_imports:
232 self.write_indent(indent + 1, f"Import: {t_import.value}")
233 for n_item in self.items:
234 n_item.dump(indent + 1)
236 def set_package(self, pkg):
237 # lobster-trace: LRM.Current_Package
238 assert isinstance(pkg, Package)
239 self.package = pkg
241 def add_import(self, mh, t_import):
242 # lobster-trace: LRM.Import_Visibility
243 # lobster-trace: LRM.Self_Imports
244 assert isinstance(mh, Message_Handler)
245 assert isinstance(t_import, Token)
246 assert t_import.kind == "IDENTIFIER"
248 if t_import.value == self.package.name:
249 mh.error(t_import.location,
250 "package %s cannot import itself" % self.package.name)
252 # Skip duplicates
253 for t_previous in self.raw_imports:
254 if t_previous.value == t_import.value:
255 mh.warning(t_import.location,
256 "duplicate import of package %s" % t_import.value)
257 return
259 self.raw_imports.append(t_import)
261 def resolve_imports(self, mh, stab):
262 # lobster-trace: LRM.Import_Visibility
263 assert isinstance(mh, Message_Handler)
264 assert isinstance(stab, Symbol_Table)
265 self.imports = set()
266 for t_import in self.raw_imports:
267 # We can ignore errors here, because that just means we
268 # generate more error later.
269 try:
270 a_import = stab.lookup(mh, t_import, Package)
271 self.imports.add(a_import)
272 a_import.set_ast_link(t_import)
273 except TRLC_Error:
274 pass
276 def is_visible(self, n_pkg):
277 # lobster-trace: LRM.Import_Visibility
278 assert self.imports is not None
279 assert isinstance(n_pkg, Package)
280 return n_pkg == self.package or n_pkg in self.imports
282 def add_item(self, node):
283 # lobster-trace: LRM.RSL_File
284 # lobster-trace: LRM.TRLC_File
285 assert isinstance(node, (Concrete_Type,
286 Check_Block,
287 Record_Object)), \
288 "trying to add %s to a compilation unit" % node.__class__.__name__
289 self.items.append(node)
292class Check(Node):
293 """User defined check
295 This represent a single user-defined check inside a check block::
297 checks T {
298 a /= null implies a > 5, warning "potato", a
299 ^^^^^^^^^^^^^^^^^^^^^^^1 ^2 ^3 ^4
301 :attribute n_type: The tuple/record type this check applies to
302 :type: Composite_Type
304 :attribute n_expr: The boolean expression for the check (see 1)
305 :type: Expression
307 :attribute n_anchor: The (optional) record component where the message \
308 should be issued (or None) (see 4)
309 :type: Composite_Component
311 :attribute severity: warning, error, or fatal (see 2; also if this is \
312 not specified the default is 'error')
313 :type: str
315 :attribute message: the user-supplied message (see 3)
316 :type: str
317 """
318 def __init__(self,
319 n_type,
320 n_expr,
321 n_anchor,
322 severity,
323 t_message,
324 extrainfo):
325 # lobster-trace: LRM.Check_Block
326 assert isinstance(n_type, Composite_Type)
327 assert isinstance(n_expr, Expression)
328 assert isinstance(n_anchor, Composite_Component) or n_anchor is None
329 assert severity in ("warning", "error", "fatal")
330 assert isinstance(t_message, Token)
331 assert t_message.kind == "STRING"
332 assert isinstance(extrainfo, str) or extrainfo is None
333 super().__init__(t_message.location)
335 self.n_type = n_type
336 self.n_expr = n_expr
337 self.n_anchor = n_anchor
338 self.severity = severity
339 # lobster-trace: LRM.No_Newlines_In_Message
340 # This is the error recovery strategy if we find newlines in
341 # the short error messages: we just remove them. The error
342 # raised is non-fatal.
343 self.message = t_message.value.replace("\n", " ")
344 self.extrainfo = extrainfo
345 self._uses_field_access = None
347 @property
348 def uses_field_access(self):
349 """Cached test: does this check's expression follow a record/union
350 reference?
352 Returns True if any sub-expression of the check expression is a
353 :class:`Field_Access_Expression` whose prefix has a
354 :class:`Record_Type` or :class:`Union_Type` type. Used by
355 the VCG to split checks into Phase A ("at declaration") and
356 Phase B ("after references").
358 :return: whether this check dereferences a record/union reference
359 :rtype: bool
361 """
362 if self._uses_field_access is None:
363 self._uses_field_access = self.n_expr.uses_field_access()
364 return self._uses_field_access
366 def dump(self, indent=0): # pragma: no cover
367 # lobster-exclude: Debugging feature
368 if self.severity == "warning":
369 self.write_indent(indent, f"Warning '{self.message}'")
370 elif self.severity == "error":
371 self.write_indent(indent, f"Error '{self.message}'")
372 else:
373 self.write_indent(indent, f"Fatal error '{self.message}'")
374 if self.n_anchor:
375 self.write_indent(indent + 1, f"Anchor: {self.n_anchor.name}")
376 self.n_expr.dump(indent + 1)
378 def get_real_location(self, composite_object):
379 # lobster-exclude: LRM.Anchoring
380 assert isinstance(composite_object, (Record_Object,
381 Tuple_Aggregate))
382 if isinstance(composite_object, Record_Object):
383 fields = composite_object.field
384 else:
385 fields = composite_object.value
387 if self.n_anchor is None or fields[self.n_anchor.name] is None:
388 return composite_object.location
389 else:
390 return fields[self.n_anchor.name].location
392 def perform(self, mh, composite_object, gstab):
393 # lobster-trace: LRM.Check_Messages
394 # lobster-trace: LRM.Check_Severity
395 assert isinstance(mh, Message_Handler)
396 assert isinstance(composite_object, (Record_Object,
397 Tuple_Aggregate))
398 assert isinstance(gstab, Symbol_Table)
400 if isinstance(composite_object, Record_Object):
401 result = self.n_expr.evaluate(mh,
402 copy(composite_object.field),
403 gstab)
404 else:
405 result = self.n_expr.evaluate(mh,
406 copy(composite_object.value),
407 gstab)
408 if result.value is None:
409 loc = self.get_real_location(composite_object)
410 mh.error(loc,
411 "check %s (%s) evaluates to null" %
412 (self.n_expr.to_string(),
413 mh.cross_file_reference(self.location)))
415 assert isinstance(result.value, bool)
417 if not result.value:
418 loc = self.get_real_location(composite_object)
419 if self.severity == "warning":
420 mh.warning(location = loc,
421 message = self.message,
422 explanation = self.extrainfo,
423 user = True)
424 else:
425 mh.error(location = loc,
426 message = self.message,
427 explanation = self.extrainfo,
428 fatal = self.severity == "fatal",
429 user = True)
430 return False
432 return True
434##############################################################################
435# AST Nodes (Expressions)
436##############################################################################
439class Unary_Operator(Enum):
440 # lobster-exclude: Utility enumeration for unary operators
441 MINUS = auto()
442 PLUS = auto()
443 LOGICAL_NOT = auto()
444 ABSOLUTE_VALUE = auto()
446 STRING_LENGTH = auto()
447 ARRAY_LENGTH = auto()
449 CONVERSION_TO_INT = auto()
450 CONVERSION_TO_DECIMAL = auto()
453class Binary_Operator(Enum):
454 # lobster-exclude: Utility enumeration for binary operators
455 LOGICAL_AND = auto() # Short-circuit
456 LOGICAL_OR = auto() # Short-circuit
457 LOGICAL_XOR = auto()
458 LOGICAL_IMPLIES = auto() # Short-circuit
460 COMP_EQ = auto()
461 COMP_NEQ = auto()
462 COMP_LT = auto()
463 COMP_LEQ = auto()
464 COMP_GT = auto()
465 COMP_GEQ = auto()
467 STRING_CONTAINS = auto()
468 STRING_STARTSWITH = auto()
469 STRING_ENDSWITH = auto()
470 STRING_REGEX = auto()
472 ARRAY_CONTAINS = auto()
474 PLUS = auto()
475 MINUS = auto()
476 TIMES = auto()
477 DIVIDE = auto()
478 REMAINDER = auto()
480 POWER = auto()
482 INDEX = auto()
485class Expression(Node, metaclass=ABCMeta):
486 """Abstract base class for all expressions.
488 :attribute typ: The type of this expression (or None for null values)
489 :type: Type
490 """
491 def __init__(self, location, typ):
492 # lobster-exclude: Constructor only declares variables
493 super().__init__(location)
494 assert typ is None or isinstance(typ, Type)
495 self.typ = typ
497 def evaluate(self, mh, context, gstab): # pragma: no cover
498 """Evaluate the expression in the given context
500 The context can be None, in which case the expression is
501 evaluated in a static context. Otherwise it must be a
502 dictionary that maps names (such as record fields or
503 quantified variables) to expressions.
505 The global symbol table must be None (for static context
506 evaluations), otherwise it must contain the global symbol
507 table to resolve record references.
509 :param mh: the message handler to use
510 :type mh: Message_Handler
511 :param context: name mapping or None (for a static context)
512 :type context: dict[str, Expression]
513 :raise TRLC_Error: if the expression cannot be evaluated
514 :return: result of the evaluation
515 :rtype: Value
517 """
518 assert isinstance(mh, Message_Handler)
519 assert context is None or isinstance(context, dict)
520 assert gstab is None or isinstance(gstab, Symbol_Table)
521 assert False, "evaluate not implemented for %s" % \
522 self.__class__.__name__
524 @abstractmethod
525 def to_string(self): # pragma: no cover
526 assert False, "to_string not implemented for %s" % \
527 self.__class__.__name__
529 def ensure_type(self, mh, typ):
530 # lobster-trace: LRM.Restricted_Null
531 # lobster-trace: LRM.Null_Is_Invalid
533 assert isinstance(typ, (type, Type))
534 if self.typ is None:
535 mh.error(self.location,
536 "null is not permitted here")
537 elif isinstance(typ, type) and not isinstance(self.typ, typ):
538 mh.error(self.location,
539 "expected expression of type %s, got %s instead" %
540 (typ.__name__,
541 self.typ.__class__.__name__))
542 elif isinstance(typ, Type) and self.typ != typ:
543 mh.error(self.location,
544 "expected expression of type %s, got %s instead" %
545 (typ.name,
546 self.typ.name))
548 def resolve_references(self, mh):
549 assert isinstance(mh, Message_Handler)
551 @abstractmethod
552 def can_be_null(self):
553 """Test if the expression could return null
555 Checks the expression if it could generate a null value
556 *without* raising an error. For example `x` could generate a
557 null value if `x` is a record component that is
558 optional. However `x + 1` could not, since an error would
559 occur earlier.
561 :return: possibility of encountering null
562 :rtype: bool
564 """
565 assert False, "can_be_null not implemented for %s" % \
566 self.__class__.__name__
568 def uses_field_access(self):
569 """Test if this expression contains a field access on a record or
570 union reference.
572 Returns True if any sub-expression is a
573 :class:`Field_Access_Expression` whose prefix has a
574 :class:`Record_Type` or :class:`Union_Type` type. This is
575 used by the VCG to split checks into "at declaration"
576 (Phase A) and "after references" (Phase B).
578 :return: whether this expression follows a record/union reference
579 :rtype: bool
581 """
582 return False
585class Implicit_Null(Expression):
586 """Synthesised null values
588 When a record object or tuple aggregate is declared and an
589 optional component or field is not specified, we synthesise an
590 implicit null expression for this.
592 For example given this TRLC type::
594 type T {
595 x optional Integer
596 }
598 And this declaration::
600 T Potato {}
602 Then the field mapping for Potato will be::
604 {x: Implicit_Null}
606 Each field will get its own implicit null. Further note that this
607 implicit null is distinct from the explicit :class:`Null_Literal`
608 that can appear in check expressions.
610 """
611 def __init__(self, composite_object, composite_component):
612 # lobster-trace: LRM.Unspecified_Optional_Components
613 assert isinstance(composite_object, (Record_Object,
614 Tuple_Aggregate))
615 assert isinstance(composite_component, Composite_Component)
616 super().__init__(composite_object.location, None)
618 def to_string(self):
619 return "null"
621 def evaluate(self, mh, context, gstab):
622 # lobster-trace: LRM.Unspecified_Optional_Components
623 assert isinstance(mh, Message_Handler)
624 assert context is None or isinstance(context, dict)
625 assert gstab is None or isinstance(gstab, Symbol_Table)
626 return Value(self.location, None, None)
628 def to_python_object(self):
629 return None
631 def dump(self, indent=0): # pragma: no cover
632 # lobster-exclude: Debugging feature
633 self.write_indent(indent, "Implicit_Null")
635 def can_be_null(self):
636 return True
639class Literal(Expression, metaclass=ABCMeta):
640 """Abstract base for all Literals
642 Does not offer any additional features, but it's a nice way to
643 group together all literal types. This is useful if you want to
644 check if you are dealing with a literal::
646 isinstance(my_expression, Literal)
648 """
649 @abstractmethod
650 def to_python_object(self):
651 assert False
654class Null_Literal(Literal):
655 # lobster-trace: LRM.Primary
656 """The null literal
658 This can appear in check expressions::
660 a /= null implies a > 5
661 ^^^^
663 Please note that this is distinct from the :class:`Implicit_Null`
664 values that appear in record objects.
666 """
667 def __init__(self, token):
668 assert isinstance(token, Token)
669 assert token.kind == "KEYWORD"
670 assert token.value == "null"
671 super().__init__(token.location, None)
673 def dump(self, indent=0): # pragma: no cover
674 self.write_indent(indent, "Null Literal")
676 def to_string(self):
677 return "null"
679 def evaluate(self, mh, context, gstab):
680 assert isinstance(mh, Message_Handler)
681 assert context is None or isinstance(context, dict)
682 assert gstab is None or isinstance(gstab, Symbol_Table)
683 return Value(self.location, None, None)
685 def to_python_object(self):
686 return None
688 def can_be_null(self):
689 return True
692class Integer_Literal(Literal):
693 # lobster-trace: LRM.Integer_Values
694 # lobster-trace: LRM.Primary
695 """Integer literals
697 Note that these are always positive. A negative integer is
698 actually a unary negation expression, operating on a positive
699 integer literal::
701 x == -5
703 This would create the following tree::
705 OP_EQUALITY
706 NAME_REFERENCE x
707 UNARY_EXPRESSION -
708 INTEGER_LITERAL 5
710 :attribute value: the non-negative integer value
711 :type: int
712 """
713 def __init__(self, token, typ):
714 assert isinstance(token, Token)
715 assert token.kind == "INTEGER"
716 assert isinstance(typ, Builtin_Integer)
717 super().__init__(token.location, typ)
719 self.value = token.value
721 def dump(self, indent=0): # pragma: no cover
722 self.write_indent(indent, f"Integer Literal {self.value}")
724 def to_string(self):
725 return str(self.value)
727 def evaluate(self, mh, context, gstab):
728 assert isinstance(mh, Message_Handler)
729 assert context is None or isinstance(context, dict)
730 assert gstab is None or isinstance(gstab, Symbol_Table)
731 return Value(self.location, self.value, self.typ)
733 def to_python_object(self):
734 return self.value
736 def can_be_null(self):
737 return False
740class Decimal_Literal(Literal):
741 # lobster-trace: LRM.Decimal_Values
742 # lobster-trace: LRM.Primary
743 """Decimal literals
745 Note that these are always positive. A negative decimal is
746 actually a unary negation expression, operating on a positive
747 decimal literal::
749 x == -5.0
751 This would create the following tree::
753 OP_EQUALITY
754 NAME_REFERENCE x
755 UNARY_EXPRESSION -
756 DECIMAL_LITERAL 5.0
758 :attribute value: the non-negative decimal value
759 :type: fractions.Fraction
760 """
761 def __init__(self, token, typ):
762 assert isinstance(token, Token)
763 assert token.kind == "DECIMAL"
764 assert isinstance(typ, Builtin_Decimal)
765 super().__init__(token.location, typ)
767 self.value = token.value
769 def dump(self, indent=0): # pragma: no cover
770 self.write_indent(indent, f"Decimal Literal {self.value}")
772 def to_string(self):
773 return str(self.value)
775 def evaluate(self, mh, context, gstab):
776 assert isinstance(mh, Message_Handler)
777 assert context is None or isinstance(context, dict)
778 assert gstab is None or isinstance(gstab, Symbol_Table)
779 return Value(self.location, self.value, self.typ)
781 def to_python_object(self):
782 return float(self.value)
784 def can_be_null(self):
785 return False
788class String_Literal(Literal):
789 # lobster-trace: LRM.String_Values
790 # lobster-trace: LRM.Markup_String_Values
791 # lobster-trace: LRM.Primary
792 """String literals
794 Note the value of the string does not include the quotation marks,
795 and any escape sequences are fully resolved. For example::
797 "foo\\"bar"
799 Will have a value of ``foo"bar``.
801 :attribute value: string content
802 :type: str
804 :attribute references: resolved references of a markup string
805 :type: list[Record_Reference]
807 """
808 def __init__(self, token, typ):
809 assert isinstance(token, Token)
810 assert token.kind == "STRING"
811 assert isinstance(typ, Builtin_String)
812 super().__init__(token.location, typ)
814 self.value = token.value
815 self.has_references = isinstance(typ, Builtin_Markup_String)
816 self.references = []
818 def dump(self, indent=0): # pragma: no cover
819 self.write_indent(indent, f"String Literal {repr(self.value)}")
820 if self.has_references:
821 self.write_indent(indent + 1, "Markup References")
822 for ref in self.references:
823 ref.dump(indent + 2)
825 def to_string(self):
826 return self.value
828 def evaluate(self, mh, context, gstab):
829 assert isinstance(mh, Message_Handler)
830 assert context is None or isinstance(context, dict)
831 assert gstab is None or isinstance(gstab, Symbol_Table)
832 return Value(self.location, self.value, self.typ)
834 def to_python_object(self):
835 return self.value
837 def resolve_references(self, mh):
838 assert isinstance(mh, Message_Handler)
839 for ref in self.references:
840 ref.resolve_references(mh)
842 def can_be_null(self):
843 return False
846class Boolean_Literal(Literal):
847 # lobster-trace: LRM.Boolean_Values
848 # lobster-trace: LRM.Primary
849 """Boolean values
851 :attribute value: the boolean value
852 :type: bool
853 """
854 def __init__(self, token, typ):
855 assert isinstance(token, Token)
856 assert token.kind == "KEYWORD"
857 assert token.value in ("false", "true")
858 assert isinstance(typ, Builtin_Boolean)
859 super().__init__(token.location, typ)
861 self.value = token.value == "true"
863 def dump(self, indent=0): # pragma: no cover
864 self.write_indent(indent, f"Boolean Literal {self.value}")
866 def to_string(self):
867 return str(self.value)
869 def evaluate(self, mh, context, gstab):
870 assert isinstance(mh, Message_Handler)
871 assert context is None or isinstance(context, dict)
872 assert gstab is None or isinstance(gstab, Symbol_Table)
873 return Value(self.location, self.value, self.typ)
875 def to_python_object(self):
876 return self.value
878 def can_be_null(self):
879 return False
882class Enumeration_Literal(Literal):
883 """Enumeration values
885 Note that this is distinct from
886 :class:`Enumeration_Literal_Spec`. An enumeration literal is a
887 specific mention of an enumeration member in an expression::
889 foo != my_enum.POTATO
890 ^^^^^^^^^^^^^^
892 To get to the string value of the enumeration literal
893 (i.e. ``POTATO`` here) you can get the name of the literal spec
894 itself: ``enum_lit.value.name``; and to get the name of the
895 enumeration (i.e. ``my_enum`` here) you can use
896 ``enum_lit.value.n_typ.name``.
898 :attribute value: enumeration value
899 :type: Enumeration_Literal_Spec
901 """
902 def __init__(self, location, literal):
903 # lobster-exclude: Constructor only declares variables
904 assert isinstance(literal, Enumeration_Literal_Spec)
905 super().__init__(location, literal.n_typ)
907 self.value = literal
909 def dump(self, indent=0): # pragma: no cover
910 # lobster-exclude: Debugging feature
911 self.write_indent(indent,
912 f"Enumeration Literal {self.typ.name}.{self.value.name}")
914 def to_string(self):
915 return self.typ.name + "." + self.value.name
917 def evaluate(self, mh, context, gstab):
918 assert isinstance(mh, Message_Handler)
919 assert context is None or isinstance(context, dict)
920 assert gstab is None or isinstance(gstab, Symbol_Table)
921 return Value(self.location, self.value, self.typ)
923 def to_python_object(self):
924 return self.value.name
926 def can_be_null(self):
927 return False
930class Array_Aggregate(Expression):
931 """Instances of array types
933 This is created when assigning to array components::
935 potatoes = ["picasso", "yukon gold", "sweet"]
936 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
938 The type of expression that can be found in an array is somewhat
939 limited:
941 * :class:`Literal`
942 * :class:`Array_Aggregate`
943 * :class:`Record_Reference`
945 :attribute value: contents of the array
946 :type: list[Expression]
948 """
949 def __init__(self, location, typ):
950 # lobster-trace: LRM.Record_Object_Declaration
952 super().__init__(location, typ)
953 self.value = []
955 def dump(self, indent=0): # pragma: no cover
956 # lobster-exclude: Debugging feature
957 self.write_indent(indent, "Array_Aggregate")
958 for n_value in self.value:
959 n_value.dump(indent + 1)
961 def append(self, value):
962 assert isinstance(value, (Literal,
963 Unary_Expression,
964 Array_Aggregate,
965 Tuple_Aggregate,
966 Record_Reference))
967 self.value.append(value)
969 def to_string(self):
970 return "[" + ", ".join(x.to_string() for x in self.value) + "]"
972 def evaluate(self, mh, context, gstab):
973 assert isinstance(mh, Message_Handler)
974 assert context is None or isinstance(context, dict)
975 assert gstab is None or isinstance(gstab, Symbol_Table)
976 return Value(self.location,
977 list(element.evaluate(mh, context, gstab)
978 for element in self.value),
979 self.typ)
981 def resolve_references(self, mh):
982 assert isinstance(mh, Message_Handler)
984 for val in self.value:
985 val.resolve_references(mh)
987 def to_python_object(self):
988 return [x.to_python_object() for x in self.value]
990 def can_be_null(self):
991 return False
993 def uses_field_access(self):
994 return any(expr.uses_field_access() for expr in self.value)
997class Tuple_Aggregate(Expression):
998 """Instances of a tuple
1000 This is created when assigning to a tuple components. There are
1001 two forms, the ordinary form::
1003 coordinate = (12.3, 40.0)
1004 ^^^^^^^^^^^^
1006 And the separator form::
1008 item = 12345@42
1009 ^^^^^^^^
1011 In terms of AST there is no difference, as the separator is only
1012 syntactic sugar.
1014 :attribute value: contents of the tuple
1015 :type: dict[str, Expression]
1017 """
1018 def __init__(self, location, typ):
1019 # lobster-trace: LRM.Unspecified_Optional_Components
1020 # lobster-trace: LRM.Record_Object_Declaration
1022 super().__init__(location, typ)
1023 self.value = {n_field.name : Implicit_Null(self, n_field)
1024 for n_field in self.typ.components.values()}
1026 def assign(self, field, value):
1027 assert isinstance(field, str)
1028 assert isinstance(value, (Literal,
1029 Unary_Expression,
1030 Tuple_Aggregate,
1031 Record_Reference)), \
1032 "value is %s" % value.__class__.__name__
1033 assert field in self.typ.components
1035 self.value[field] = value
1037 def dump(self, indent=0): # pragma: no cover
1038 # lobster-exclude: Debugging feature
1039 self.write_indent(indent, "Tuple_Aggregate")
1040 self.write_indent(indent + 1, f"Type: {self.typ.name}")
1041 for n_item in self.typ.iter_sequence():
1042 if isinstance(n_item, Composite_Component):
1043 self.value[n_item.name].dump(indent + 1)
1045 def to_string(self):
1046 first = True
1047 if self.typ.has_separators():
1048 rv = ""
1049 else:
1050 rv = "("
1051 for n_item in self.typ.iter_sequence():
1052 if isinstance(n_item, Separator):
1053 rv += " %s " % n_item.token.value
1054 elif first:
1055 first = False
1056 else:
1057 rv += ", "
1059 if isinstance(n_item, Composite_Component):
1060 rv += self.value[n_item.name].to_string()
1061 if self.typ.has_separators():
1062 rv = ""
1063 else:
1064 rv = ")"
1065 return rv
1067 def evaluate(self, mh, context, gstab):
1068 assert isinstance(mh, Message_Handler)
1069 assert context is None or isinstance(context, dict)
1070 assert gstab is None or isinstance(gstab, Symbol_Table)
1071 return Value(self.location,
1072 {name : element.evaluate(mh, context, gstab)
1073 for name, element in self.value.items()},
1074 self.typ)
1076 def resolve_references(self, mh):
1077 assert isinstance(mh, Message_Handler)
1079 for val in self.value.values():
1080 val.resolve_references(mh)
1082 def to_python_object(self):
1083 return {name: value.to_python_object()
1084 for name, value in self.value.items()}
1086 def can_be_null(self):
1087 return False
1089 def uses_field_access(self):
1090 return any(expr.uses_field_access()
1091 for expr in self.value.values())
1094class Record_Reference(Expression):
1095 """Reference to another record object
1097 This can appear in record object declarations::
1099 Requirement Kitten {
1100 depends_on = Other_Package.Cat
1101 ^1 ^2
1102 }
1104 Note that this is distinct from :class:`Record_Object`. It is just
1105 the name; to get to the object referred to by this you can consult
1106 the target attribute.
1108 The reason we have this indirection is that not all names can be
1109 immediately resolved on parsing in the TRLC language.
1111 Note that while the containing package (see 1) is optional in the
1112 source language, the containing package will always be filled in
1113 in this AST node.
1115 :attribute name: The name of the record (see 2)
1116 :type: str
1118 :attribute target: The concrete record object referred to by (2)
1119 :type: Record_Object
1121 :attribute package: The package (see 1) supposed to contain (2)
1122 :type: Package
1124 """
1125 def __init__(self, location, name, typ, package):
1126 # lobster-exclude: Constructor only declares variables
1127 assert isinstance(location, Location)
1128 assert isinstance(name, str)
1129 assert isinstance(typ, (Record_Type, Union_Type)) or typ is None
1130 assert isinstance(package, Package)
1131 super().__init__(location, typ)
1133 self.name = name
1134 self.target = None
1135 self.package = package
1137 def dump(self, indent=0): # pragma: no cover
1138 # lobster-exclude: Debugging feature
1139 self.write_indent(indent, f"Record Reference {self.name}")
1140 self.write_indent(indent + 1,
1141 f"Resolved: {self.target is not None}")
1143 def to_string(self):
1144 return self.name
1146 def evaluate(self, mh, context, gstab):
1147 assert isinstance(mh, Message_Handler)
1148 assert context is None or isinstance(context, dict)
1149 assert gstab is None or isinstance(gstab, Symbol_Table)
1150 return Value(self.location, copy(self.target.field), self.typ)
1152 def resolve_references(self, mh):
1153 # lobster-trace: LRM.References_To_Extensions
1154 # lobster-trace: LRM.Union_Type_Minimum_Members
1155 assert isinstance(mh, Message_Handler)
1157 self.target = self.package.symbols.lookup_direct(
1158 mh = mh,
1159 name = self.name,
1160 error_location = self.location,
1161 required_subclass = Record_Object)
1162 if self.typ is None:
1163 self.typ = self.target.n_typ
1164 elif isinstance(self.typ, Union_Type):
1165 if not self.typ.is_compatible(self.target.n_typ):
1166 mh.error(self.location,
1167 "expected reference of type %s,"
1168 " but %s is of type %s" %
1169 (self.typ.name,
1170 self.target.name,
1171 self.target.n_typ.name))
1172 elif not self.target.n_typ.is_subclass_of(self.typ):
1173 mh.error(self.location,
1174 "expected reference of type %s, but %s is of type %s" %
1175 (self.typ.name,
1176 self.target.name,
1177 self.target.n_typ.name))
1179 def to_python_object(self):
1180 return self.target.fully_qualified_name()
1182 def can_be_null(self):
1183 return False
1186class Name_Reference(Expression):
1187 # lobster-trace: LRM.Qualified_Name
1188 # lobster-trace: LRM.Static_Regular_Expression
1190 """Reference to a name
1192 Name reference to either a :class:`Composite_Component` or a
1193 :class:`Quantified_Variable`. The actual value of course depends
1194 on the context. See :py:meth:`Expression.evaluate()`.
1196 For example::
1198 (forall x in potato => x > 1)
1199 ^1 ^2
1201 Both indicated parts are a :class:`Name_Reference`, the first one
1202 refers to a :class:`Composite_Component`, and the second refers to a
1203 :class:`Quantified_Variable`.
1205 :attribute entity: the entity named here
1206 :type: Composite_Component, Quantified_Variable
1207 """
1208 def __init__(self, location, entity):
1209 assert isinstance(entity, (Composite_Component,
1210 Quantified_Variable))
1211 super().__init__(location, entity.n_typ)
1212 self.entity = entity
1214 def dump(self, indent=0): # pragma: no cover
1215 self.write_indent(indent, f"Name Reference to {self.entity.name}")
1217 def to_string(self):
1218 return self.entity.name
1220 def evaluate(self, mh, context, gstab):
1221 assert isinstance(mh, Message_Handler)
1222 assert context is None or isinstance(context, dict)
1223 assert gstab is None or isinstance(gstab, Symbol_Table)
1225 if context is None:
1226 mh.error(self.location,
1227 "cannot be used in a static context")
1229 assert self.entity.name in context
1230 return context[self.entity.name].evaluate(mh, context, gstab)
1232 def can_be_null(self):
1233 # The only way we could generate null here (without raising
1234 # error earlier) is when we refer to a component that is
1235 # optional.
1236 if isinstance(self.entity, Composite_Component):
1237 return self.entity.optional
1238 else:
1239 return False
1242class Unary_Expression(Expression):
1243 """Expression with only one operand
1245 This captures the following operations:
1247 * Unary_Operator.PLUS (e.g. ``+5``)
1248 * Unary_Operator.MINUS (e.g. ``-5``)
1249 * Unary_Operator.ABSOLUTE_VALUE (e.g. ``abs 42``)
1250 * Unary_Operator.LOGICAL_NOT (e.g. ``not True``)
1251 * Unary_Operator.STRING_LENGTH (e.g. ``len("foobar")``)
1252 * Unary_Operator.ARRAY_LENGTH (e.g. ``len(component_name)``)
1253 * Unary_Operator.CONVERSION_TO_INT (e.g. ``Integer(5.3)``)
1254 * Unary_Operator.CONVERSION_TO_DECIMAL (e.g. ``Decimal(5)``)
1256 Note that several builtin functions are mapped to unary operators.
1258 :attribute operator: the operation
1259 :type: Unary_Operator
1261 :attribute n_operand: the expression we operate on
1262 :type: Expression
1264 """
1265 def __init__(self, mh, location, typ, operator, n_operand):
1266 # lobster-trace: LRM.Simple_Expression
1267 # lobster-trace: LRM.Relation
1268 # lobster-trace: LRM.Factor
1269 # lobster-trace: LRM.Signature_Len
1270 # lobster-trace: LRM.Signature_Type_Conversion
1272 super().__init__(location, typ)
1273 assert isinstance(mh, Message_Handler)
1274 assert isinstance(operator, Unary_Operator)
1275 assert isinstance(n_operand, Expression)
1276 self.operator = operator
1277 self.n_operand = n_operand
1279 if operator in (Unary_Operator.MINUS,
1280 Unary_Operator.PLUS,
1281 Unary_Operator.ABSOLUTE_VALUE):
1282 self.n_operand.ensure_type(mh, Builtin_Numeric_Type)
1283 elif operator == Unary_Operator.LOGICAL_NOT:
1284 self.n_operand.ensure_type(mh, Builtin_Boolean)
1285 elif operator == Unary_Operator.STRING_LENGTH:
1286 self.n_operand.ensure_type(mh, Builtin_String)
1287 elif operator == Unary_Operator.ARRAY_LENGTH:
1288 self.n_operand.ensure_type(mh, Array_Type)
1289 elif operator == Unary_Operator.CONVERSION_TO_INT:
1290 self.n_operand.ensure_type(mh, Builtin_Numeric_Type)
1291 elif operator == Unary_Operator.CONVERSION_TO_DECIMAL:
1292 self.n_operand.ensure_type(mh, Builtin_Numeric_Type)
1293 else:
1294 mh.ice_loc(self.location,
1295 "unexpected unary operation %s" % operator)
1297 def to_string(self):
1298 prefix_operators = {
1299 Unary_Operator.MINUS : "-",
1300 Unary_Operator.PLUS : "+",
1301 Unary_Operator.ABSOLUTE_VALUE : "abs ",
1302 Unary_Operator.LOGICAL_NOT : "not ",
1303 }
1304 function_calls = {
1305 Unary_Operator.STRING_LENGTH : "len",
1306 Unary_Operator.ARRAY_LENGTH : "len",
1307 Unary_Operator.CONVERSION_TO_INT : "Integer",
1308 Unary_Operator.CONVERSION_TO_DECIMAL : "Decimal"
1309 }
1311 if self.operator in prefix_operators:
1312 return prefix_operators[self.operator] + \
1313 self.n_operand.to_string()
1315 elif self.operator in function_calls:
1316 return "%s(%s)" % (function_calls[self.operator],
1317 self.n_operand.to_string())
1319 else:
1320 assert False
1322 def dump(self, indent=0): # pragma: no cover
1323 # lobster-exclude: Debugging feature
1324 self.write_indent(indent, f"Unary {self.operator} Expression")
1325 self.write_indent(indent + 1, f"Type: {self.typ.name}")
1326 self.n_operand.dump(indent + 1)
1328 def evaluate(self, mh, context, gstab):
1329 # lobster-trace: LRM.Null_Is_Invalid
1330 # lobster-trace: LRM.Signature_Len
1331 # lobster-trace: LRM.Signature_Type_Conversion
1332 # lobster-trace: LRM.Len_Semantics
1333 # lobster-trace: LRM.Integer_Conversion_Semantics
1334 # lobster-trace: LRM.Decimal_Conversion_Semantics
1336 assert isinstance(mh, Message_Handler)
1337 assert context is None or isinstance(context, dict)
1338 assert gstab is None or isinstance(gstab, Symbol_Table)
1340 v_operand = self.n_operand.evaluate(mh, context, gstab)
1341 if v_operand.value is None: 1341 ↛ 1342line 1341 didn't jump to line 1342 because the condition on line 1341 was never true
1342 mh.error(v_operand.location,
1343 "input to unary expression %s (%s) must not be null" %
1344 (self.to_string(),
1345 mh.cross_file_reference(self.location)))
1347 if self.operator == Unary_Operator.MINUS:
1348 return Value(location = self.location,
1349 value = -v_operand.value,
1350 typ = self.typ)
1351 elif self.operator == Unary_Operator.PLUS:
1352 return Value(location = self.location,
1353 value = +v_operand.value,
1354 typ = self.typ)
1355 elif self.operator == Unary_Operator.LOGICAL_NOT:
1356 return Value(location = self.location,
1357 value = not v_operand.value,
1358 typ = self.typ)
1359 elif self.operator == Unary_Operator.ABSOLUTE_VALUE:
1360 return Value(location = self.location,
1361 value = abs(v_operand.value),
1362 typ = self.typ)
1363 elif self.operator in (Unary_Operator.STRING_LENGTH,
1364 Unary_Operator.ARRAY_LENGTH):
1365 return Value(location = self.location,
1366 value = len(v_operand.value),
1367 typ = self.typ)
1368 elif self.operator == Unary_Operator.CONVERSION_TO_INT:
1369 if isinstance(v_operand.value, Fraction): 1369 ↛ 1375line 1369 didn't jump to line 1375 because the condition on line 1369 was always true
1370 return Value(
1371 location = self.location,
1372 value = math.round_nearest_away(v_operand.value),
1373 typ = self.typ)
1374 else:
1375 return Value(location = self.location,
1376 value = v_operand.value,
1377 typ = self.typ)
1378 elif self.operator == Unary_Operator.CONVERSION_TO_DECIMAL:
1379 return Value(location = self.location,
1380 value = Fraction(v_operand.value),
1381 typ = self.typ)
1382 else:
1383 mh.ice_loc(self.location,
1384 "unexpected unary operation %s" % self.operator)
1386 def to_python_object(self):
1387 assert self.operator in (Unary_Operator.MINUS,
1388 Unary_Operator.PLUS)
1389 val = self.n_operand.to_python_object()
1390 if self.operator == Unary_Operator.MINUS:
1391 return -val
1392 else:
1393 return val
1395 def can_be_null(self):
1396 return False
1398 def uses_field_access(self):
1399 return self.n_operand.uses_field_access()
1402class Binary_Expression(Expression):
1403 """Expression with two operands
1405 This captures the following operations:
1407 * Binary_Operator.LOGICAL_AND (e.g. ``a and b``)
1408 * Binary_Operator.LOGICAL_OR (e.g. ``a or b``)
1409 * Binary_Operator.LOGICAL_XOR (e.g. ``a xor b``)
1410 * Binary_Operator.LOGICAL_IMPLIES (e.g. ``a implies b``)
1411 * Binary_Operator.COMP_EQ (e.g. ``a == null``)
1412 * Binary_Operator.COMP_NEQ (e.g. ``a != null``)
1413 * Binary_Operator.COMP_LT (e.g. ``1 < 2``)
1414 * Binary_Operator.COMP_LEQ (e.g. ``1 <= 2``)
1415 * Binary_Operator.COMP_GT (e.g. ``a > b``)
1416 * Binary_Operator.COMP_GEQ (e.g. ``a >= b``)
1417 * Binary_Operator.STRING_CONTAINS (e.g. ``"foo" in "foobar"``)
1418 * Binary_Operator.STRING_STARTSWITH (e.g. ``startswith("foo", "f")``)
1419 * Binary_Operator.STRING_ENDSWITH (e.g. ``endswith("foo", "o")``)
1420 * Binary_Operator.STRING_REGEX (e.g. ``matches("foo", ".o.``)
1421 * Binary_Operator.ARRAY_CONTAINS (e.g. ``42 in arr``)
1422 * Binary_Operator.PLUS (e.g. ``42 + b`` or ``"foo" + bar``)
1423 * Binary_Operator.MINUS (e.g. ``a - 1``)
1424 * Binary_Operator.TIMES (e.g. ``2 * x``)
1425 * Binary_Operator.DIVIDE (e.g. ``x / 2``)
1426 * Binary_Operator.REMAINDER (e.g. ``x % 2``)
1427 * Binary_Operator.POWER (e.g. ``x ** 2``)
1428 * Binary_Operator.INDEX (e.g. ``foo[2]``)
1430 Note that several builtin functions are mapped to unary operators.
1432 Note also that the plus operation is supported for integers,
1433 rationals and strings.
1435 :attribute operator: the operation
1436 :type: Binary_Operator
1438 :attribute n_lhs: the first operand
1439 :type: Expression
1441 :attribute n_rhs: the second operand
1442 :type: Expression
1444 """
1445 def __init__(self, mh, location, typ, operator, n_lhs, n_rhs):
1446 # lobster-trace: LRM.Expression
1447 # lobster-trace: LRM.Relation
1448 # lobster-trace: LRM.Simple_Expression
1449 # lobster-trace: LRM.Term
1450 # lobster-trace: LRM.Factor
1451 # lobster-trace: LRM.Signature_String_End_Functions
1452 # lobster-trace: LRM.Signature_Matches
1454 super().__init__(location, typ)
1455 assert isinstance(mh, Message_Handler)
1456 assert isinstance(operator, Binary_Operator)
1457 assert isinstance(n_lhs, Expression)
1458 assert isinstance(n_rhs, Expression)
1459 self.operator = operator
1460 self.n_lhs = n_lhs
1461 self.n_rhs = n_rhs
1463 if operator in (Binary_Operator.LOGICAL_AND,
1464 Binary_Operator.LOGICAL_OR,
1465 Binary_Operator.LOGICAL_XOR,
1466 Binary_Operator.LOGICAL_IMPLIES):
1467 self.n_lhs.ensure_type(mh, Builtin_Boolean)
1468 self.n_rhs.ensure_type(mh, Builtin_Boolean)
1470 elif operator in (Binary_Operator.COMP_EQ,
1471 Binary_Operator.COMP_NEQ):
1472 # lobster-trace: LRM.Union_Type_Equality
1473 # lobster-trace: LRM.Union_Type_Equality_Domain
1474 if (self.n_lhs.typ is None) or (self.n_rhs.typ is None):
1475 # We can compary anything to null (including itself)
1476 pass
1477 elif isinstance(self.n_lhs.typ, Union_Type) or \
1478 isinstance(self.n_rhs.typ, Union_Type):
1479 # For union types, we allow comparison if both
1480 # sides are record-like (Record_Type or Union_Type)
1481 lhs_is_record = isinstance(self.n_lhs.typ,
1482 (Record_Type, Union_Type))
1483 rhs_is_record = isinstance(self.n_rhs.typ,
1484 (Record_Type, Union_Type))
1485 if not (lhs_is_record and rhs_is_record): 1485 ↛ 1486line 1485 didn't jump to line 1486 because the condition on line 1485 was never true
1486 mh.error(self.location,
1487 "type mismatch: %s and %s do not match" %
1488 (self.n_lhs.typ.name,
1489 self.n_rhs.typ.name))
1490 else:
1491 # Check that there is at least one pair of member
1492 # types (one from each side) where one is a subtype
1493 # of the other. This implements Equality_Domain for
1494 # union types: an unrelated record type is rejected.
1495 lhs_members = (self.n_lhs.typ.types
1496 if isinstance(self.n_lhs.typ, Union_Type)
1497 else [self.n_lhs.typ])
1498 rhs_members = (self.n_rhs.typ.types
1499 if isinstance(self.n_rhs.typ, Union_Type)
1500 else [self.n_rhs.typ])
1501 if not any(lm.is_subclass_of(rm) or rm.is_subclass_of(lm)
1502 for lm in lhs_members
1503 for rm in rhs_members):
1504 mh.error(self.location,
1505 "type mismatch: %s and %s do not match" %
1506 (self.n_lhs.typ.name,
1507 self.n_rhs.typ.name))
1508 elif self.n_lhs.typ != self.n_rhs.typ:
1509 # Otherwise we can compare anything, as long as the
1510 # types match
1511 mh.error(self.location,
1512 "type mismatch: %s and %s do not match" %
1513 (self.n_lhs.typ.name,
1514 self.n_rhs.typ.name))
1516 elif operator in (Binary_Operator.COMP_LT,
1517 Binary_Operator.COMP_LEQ,
1518 Binary_Operator.COMP_GT,
1519 Binary_Operator.COMP_GEQ):
1520 self.n_lhs.ensure_type(mh, Builtin_Numeric_Type)
1521 self.n_rhs.ensure_type(mh, self.n_lhs.typ)
1523 elif operator in (Binary_Operator.STRING_CONTAINS,
1524 Binary_Operator.STRING_STARTSWITH,
1525 Binary_Operator.STRING_ENDSWITH,
1526 Binary_Operator.STRING_REGEX):
1527 self.n_lhs.ensure_type(mh, Builtin_String)
1528 self.n_rhs.ensure_type(mh, Builtin_String)
1530 elif operator == Binary_Operator.ARRAY_CONTAINS:
1531 self.n_rhs.ensure_type(mh, Array_Type)
1532 self.n_lhs.ensure_type(mh, self.n_rhs.typ.element_type.__class__)
1534 elif operator == Binary_Operator.PLUS:
1535 if isinstance(self.n_lhs.typ, Builtin_Numeric_Type):
1536 self.n_rhs.ensure_type(mh, self.n_lhs.typ)
1537 else:
1538 self.n_lhs.ensure_type(mh, Builtin_String)
1539 self.n_rhs.ensure_type(mh, Builtin_String)
1541 elif operator in (Binary_Operator.MINUS,
1542 Binary_Operator.TIMES,
1543 Binary_Operator.DIVIDE):
1544 self.n_lhs.ensure_type(mh, Builtin_Numeric_Type)
1545 self.n_rhs.ensure_type(mh, self.n_lhs.typ)
1547 elif operator == Binary_Operator.POWER:
1548 self.n_lhs.ensure_type(mh, Builtin_Numeric_Type)
1549 self.n_rhs.ensure_type(mh, Builtin_Integer)
1551 elif operator == Binary_Operator.REMAINDER:
1552 self.n_lhs.ensure_type(mh, Builtin_Integer)
1553 self.n_rhs.ensure_type(mh, Builtin_Integer)
1555 elif operator == Binary_Operator.INDEX:
1556 self.n_lhs.ensure_type(mh, Array_Type)
1557 self.n_rhs.ensure_type(mh, Builtin_Integer)
1559 else:
1560 mh.ice_loc(self.location,
1561 "unexpected binary operation %s" % operator)
1563 def dump(self, indent=0): # pragma: no cover
1564 # lobster-exclude: Debugging feature
1565 self.write_indent(indent, f"Binary {self.operator} Expression")
1566 self.write_indent(indent + 1, f"Type: {self.typ.name}")
1567 self.n_lhs.dump(indent + 1)
1568 self.n_rhs.dump(indent + 1)
1570 def to_string(self):
1571 infix_operators = {
1572 Binary_Operator.LOGICAL_AND : "and",
1573 Binary_Operator.LOGICAL_OR : "or",
1574 Binary_Operator.LOGICAL_XOR : "xor",
1575 Binary_Operator.LOGICAL_IMPLIES : "implies",
1576 Binary_Operator.COMP_EQ : "==",
1577 Binary_Operator.COMP_NEQ : "!=",
1578 Binary_Operator.COMP_LT : "<",
1579 Binary_Operator.COMP_LEQ : "<=",
1580 Binary_Operator.COMP_GT : ">",
1581 Binary_Operator.COMP_GEQ : ">=",
1582 Binary_Operator.STRING_CONTAINS : "in",
1583 Binary_Operator.ARRAY_CONTAINS : "in",
1584 Binary_Operator.PLUS : "+",
1585 Binary_Operator.MINUS : "-",
1586 Binary_Operator.TIMES : "*",
1587 Binary_Operator.DIVIDE : "/",
1588 Binary_Operator.REMAINDER : "%",
1589 Binary_Operator.POWER : "**",
1590 }
1591 string_functions = {
1592 Binary_Operator.STRING_STARTSWITH : "startswith",
1593 Binary_Operator.STRING_ENDSWITH : "endswith",
1594 Binary_Operator.STRING_REGEX : "matches",
1595 }
1597 if self.operator in infix_operators:
1598 return "%s %s %s" % (self.n_lhs.to_string(),
1599 infix_operators[self.operator],
1600 self.n_rhs.to_string())
1602 elif self.operator in string_functions:
1603 return "%s(%s, %s)" % (string_functions[self.operator],
1604 self.n_lhs.to_string(),
1605 self.n_rhs.to_string())
1607 elif self.operator == Binary_Operator.INDEX:
1608 return "%s[%s]" % (self.n_lhs.to_string(),
1609 self.n_rhs.to_string())
1611 else:
1612 assert False
1614 def evaluate(self, mh, context, gstab):
1615 # lobster-trace: LRM.Null_Equivalence
1616 # lobster-trace: LRM.Null_Is_Invalid
1617 # lobster-trace: LRM.Signature_String_End_Functions
1618 # lobster-trace: LRM.Signature_Matches
1619 # lobster-trace: LRM.Startswith_Semantics
1620 # lobster-trace: LRM.Endswith_Semantics
1621 # lobster-trace: LRM.Matches_Semantics
1623 assert isinstance(mh, Message_Handler)
1624 assert context is None or isinstance(context, dict)
1625 assert gstab is None or isinstance(gstab, Symbol_Table)
1627 v_lhs = self.n_lhs.evaluate(mh, context, gstab)
1628 if v_lhs.value is None and \
1629 self.operator not in (Binary_Operator.COMP_EQ,
1630 Binary_Operator.COMP_NEQ):
1631 mh.error(v_lhs.location,
1632 "lhs of check %s (%s) must not be null" %
1633 (self.to_string(),
1634 mh.cross_file_reference(self.location)))
1636 # Check for the short-circuit operators first
1637 if self.operator == Binary_Operator.LOGICAL_AND:
1638 assert isinstance(v_lhs.value, bool)
1639 if v_lhs.value:
1640 return self.n_rhs.evaluate(mh, context, gstab)
1641 else:
1642 return v_lhs
1644 elif self.operator == Binary_Operator.LOGICAL_OR:
1645 assert isinstance(v_lhs.value, bool)
1646 if v_lhs.value:
1647 return v_lhs
1648 else:
1649 return self.n_rhs.evaluate(mh, context, gstab)
1651 elif self.operator == Binary_Operator.LOGICAL_IMPLIES:
1652 assert isinstance(v_lhs.value, bool)
1653 if v_lhs.value:
1654 return self.n_rhs.evaluate(mh, context, gstab)
1655 else:
1656 return Value(location = self.location,
1657 value = True,
1658 typ = self.typ)
1660 # Otherwise, evaluate RHS and do the operation
1661 v_rhs = self.n_rhs.evaluate(mh, context, gstab)
1662 if v_rhs.value is None and \
1663 self.operator not in (Binary_Operator.COMP_EQ,
1664 Binary_Operator.COMP_NEQ):
1665 mh.error(v_rhs.location,
1666 "rhs of check %s (%s) must not be null" %
1667 (self.to_string(),
1668 mh.cross_file_reference(self.location)))
1670 if self.operator == Binary_Operator.LOGICAL_XOR:
1671 assert isinstance(v_lhs.value, bool)
1672 assert isinstance(v_rhs.value, bool)
1673 return Value(location = self.location,
1674 value = v_lhs.value ^ v_rhs.value,
1675 typ = self.typ)
1677 elif self.operator == Binary_Operator.COMP_EQ:
1678 return Value(location = self.location,
1679 value = v_lhs.value == v_rhs.value,
1680 typ = self.typ)
1682 elif self.operator == Binary_Operator.COMP_NEQ:
1683 return Value(location = self.location,
1684 value = v_lhs.value != v_rhs.value,
1685 typ = self.typ)
1687 elif self.operator in (Binary_Operator.COMP_LT,
1688 Binary_Operator.COMP_LEQ,
1689 Binary_Operator.COMP_GT,
1690 Binary_Operator.COMP_GEQ):
1691 return Value(
1692 location = self.location,
1693 value = {
1694 Binary_Operator.COMP_LT : lambda lhs, rhs: lhs < rhs,
1695 Binary_Operator.COMP_LEQ : lambda lhs, rhs: lhs <= rhs,
1696 Binary_Operator.COMP_GT : lambda lhs, rhs: lhs > rhs,
1697 Binary_Operator.COMP_GEQ : lambda lhs, rhs: lhs >= rhs,
1698 }[self.operator](v_lhs.value, v_rhs.value),
1699 typ = self.typ)
1701 elif self.operator == Binary_Operator.STRING_CONTAINS:
1702 assert isinstance(v_lhs.value, str)
1703 assert isinstance(v_rhs.value, str)
1705 return Value(location = self.location,
1706 value = v_lhs.value in v_rhs.value,
1707 typ = self.typ)
1709 elif self.operator == Binary_Operator.STRING_STARTSWITH:
1710 assert isinstance(v_lhs.value, str)
1711 assert isinstance(v_rhs.value, str)
1712 return Value(location = self.location,
1713 value = v_lhs.value.startswith(v_rhs.value),
1714 typ = self.typ)
1716 elif self.operator == Binary_Operator.STRING_ENDSWITH:
1717 assert isinstance(v_lhs.value, str)
1718 assert isinstance(v_rhs.value, str)
1719 return Value(location = self.location,
1720 value = v_lhs.value.endswith(v_rhs.value),
1721 typ = self.typ)
1723 elif self.operator == Binary_Operator.STRING_REGEX:
1724 assert isinstance(v_lhs.value, str)
1725 assert isinstance(v_rhs.value, str)
1726 return Value(location = self.location,
1727 value = re.match(v_rhs.value,
1728 v_lhs.value) is not None,
1729 typ = self.typ)
1731 elif self.operator == Binary_Operator.ARRAY_CONTAINS:
1732 assert isinstance(v_rhs.value, list)
1734 return Value(location = self.location,
1735 value = v_lhs in v_rhs.value,
1736 typ = self.typ)
1738 elif self.operator == Binary_Operator.PLUS:
1739 assert isinstance(v_lhs.value, (int, str, Fraction))
1740 assert isinstance(v_rhs.value, (int, str, Fraction))
1741 return Value(location = self.location,
1742 value = v_lhs.value + v_rhs.value,
1743 typ = self.typ)
1745 elif self.operator == Binary_Operator.MINUS:
1746 assert isinstance(v_lhs.value, (int, Fraction))
1747 assert isinstance(v_rhs.value, (int, Fraction))
1748 return Value(location = self.location,
1749 value = v_lhs.value - v_rhs.value,
1750 typ = self.typ)
1752 elif self.operator == Binary_Operator.TIMES:
1753 assert isinstance(v_lhs.value, (int, Fraction))
1754 assert isinstance(v_rhs.value, (int, Fraction))
1755 return Value(location = self.location,
1756 value = v_lhs.value * v_rhs.value,
1757 typ = self.typ)
1759 elif self.operator == Binary_Operator.DIVIDE:
1760 assert isinstance(v_lhs.value, (int, Fraction))
1761 assert isinstance(v_rhs.value, (int, Fraction))
1763 if v_rhs.value == 0: 1763 ↛ 1764line 1763 didn't jump to line 1764 because the condition on line 1763 was never true
1764 mh.error(v_rhs.location,
1765 "division by zero in %s (%s)" %
1766 (self.to_string(),
1767 mh.cross_file_reference(self.location)))
1769 if isinstance(v_lhs.value, int):
1770 return Value(location = self.location,
1771 value = v_lhs.value // v_rhs.value,
1772 typ = self.typ)
1773 else:
1774 return Value(location = self.location,
1775 value = v_lhs.value / v_rhs.value,
1776 typ = self.typ)
1778 elif self.operator == Binary_Operator.REMAINDER:
1779 assert isinstance(v_lhs.value, int)
1780 assert isinstance(v_rhs.value, int)
1782 if v_rhs.value == 0: 1782 ↛ 1783line 1782 didn't jump to line 1783 because the condition on line 1782 was never true
1783 mh.error(v_rhs.location,
1784 "division by zero in %s (%s)" %
1785 (self.to_string(),
1786 mh.cross_file_reference(self.location)))
1788 return Value(location = self.location,
1789 value = math.remainder(v_lhs.value, v_rhs.value),
1790 typ = self.typ)
1792 elif self.operator == Binary_Operator.POWER:
1793 assert isinstance(v_lhs.value, (int, Fraction))
1794 assert isinstance(v_rhs.value, int)
1795 return Value(location = self.location,
1796 value = v_lhs.value ** v_rhs.value,
1797 typ = self.typ)
1799 elif self.operator == Binary_Operator.INDEX:
1800 assert isinstance(v_lhs.value, list)
1801 assert isinstance(v_rhs.value, int)
1803 if v_rhs.value < 0: 1803 ↛ 1804line 1803 didn't jump to line 1804 because the condition on line 1803 was never true
1804 mh.error(v_rhs.location,
1805 "index cannot be less than zero in %s (%s)" %
1806 (self.to_string(),
1807 mh.cross_file_reference(self.location)))
1808 elif v_lhs.typ.upper_bound is not None and \ 1808 ↛ 1810line 1808 didn't jump to line 1810 because the condition on line 1808 was never true
1809 v_rhs.value > v_lhs.typ.upper_bound:
1810 mh.error(v_rhs.location,
1811 "index cannot be more than %u in %s (%s)" %
1812 (v_lhs.typ.upper_bound,
1813 self.to_string(),
1814 mh.cross_file_reference(self.location)))
1815 elif v_rhs.value > len(v_lhs.value): 1815 ↛ 1816line 1815 didn't jump to line 1816 because the condition on line 1815 was never true
1816 mh.error(v_lhs.location,
1817 "array is not big enough in %s (%s)" %
1818 (self.to_string(),
1819 mh.cross_file_reference(self.location)))
1821 return Value(location = self.location,
1822 value = v_lhs.value[v_rhs.value].value,
1823 typ = self.typ)
1825 else:
1826 mh.ice_loc(self.location,
1827 "unexpected binary operator %s" % self.operator)
1829 def can_be_null(self):
1830 return False
1832 def uses_field_access(self):
1833 return (self.n_lhs.uses_field_access() or
1834 self.n_rhs.uses_field_access())
1837class Field_Access_Expression(Expression):
1838 """Tuple, Record, or Union field access
1840 For example in::
1842 foo.bar
1843 ^1 ^2
1845 :attribute n_prefix: expression with tuple, record, or union type (see 1)
1846 :type: Expression
1848 :attribute n_field: a field to dereference (see 2)
1849 :type: Composite_Component
1851 :attribute is_union_access: True if the prefix is a union type
1852 :type: bool
1854 :attribute is_universal: True if field exists in all union members.
1855 Only meaningful when is_union_access is True.
1856 :type: bool
1858 """
1859 def __init__(self, mh, location, n_prefix, n_field,
1860 is_union_access=False, is_universal=True):
1861 # lobster-trace: LRM.Union_Type_Field_Access
1862 assert isinstance(mh, Message_Handler)
1863 assert isinstance(n_prefix, Expression)
1864 assert isinstance(n_field, Composite_Component)
1865 assert isinstance(is_union_access, bool)
1866 assert isinstance(is_universal, bool)
1867 super().__init__(location, n_field.n_typ)
1868 self.n_prefix = n_prefix
1869 self.n_field = n_field
1870 self.is_union_access = is_union_access
1871 self.is_universal = is_universal
1873 if not is_union_access:
1874 self.n_prefix.ensure_type(mh, self.n_field.member_of)
1876 def dump(self, indent=0): # pragma: no cover
1877 # lobster-exclude: Debugging feature
1878 self.write_indent(indent, f"Field_Access ({self.n_field.name})")
1879 self.n_prefix.dump(indent + 1)
1881 def to_string(self):
1882 return self.n_prefix.to_string() + "." + self.n_field.name
1884 def evaluate(self, mh, context, gstab):
1885 assert isinstance(mh, Message_Handler)
1886 assert context is None or isinstance(context, dict)
1887 assert gstab is None or isinstance(gstab, Symbol_Table)
1889 v_prefix = self.n_prefix.evaluate(mh,
1890 context,
1891 gstab).value
1892 if v_prefix is None:
1893 # lobster-trace: LRM.Dereference
1894 mh.error(self.n_prefix.location,
1895 "null dereference")
1897 # lobster-trace: LRM.Union_Type_Partial_Field_Access
1898 # lobster-trace: LRM.Union_Type_Partial_Field_Null
1899 if self.n_field.name not in v_prefix:
1900 return Value(self.location, None, None)
1902 v_field = v_prefix[self.n_field.name]
1903 if isinstance(v_field, Expression):
1904 # lobster-trace: LRM.Dereference
1905 return v_field.evaluate(mh, context, gstab)
1906 else:
1907 return v_field
1909 def can_be_null(self):
1910 # A union field access on a partial field (not present in all
1911 # member types) evaluates to null at runtime, so we must
1912 # report True in that case.
1913 return self.is_union_access and not self.is_universal
1915 def uses_field_access(self):
1916 # lobster-trace: LRM.Dereference
1917 if isinstance(self.n_prefix.typ, (Record_Type, Union_Type)):
1918 return True
1919 return self.n_prefix.uses_field_access()
1922class Range_Test(Expression):
1923 """Range membership test
1925 For example in::
1927 x in 1 .. field+1
1928 ^lhs ^lower ^^^^^^^upper
1930 Note that none of these are guaranteed to be literals or names;
1931 you can have arbitrarily complex expressions here.
1933 :attribute n_lhs: the expression to test
1934 :type: Expression
1936 :attribute n_lower: the lower bound
1937 :type: Expression
1939 :attribute n_upper: the upper bound
1940 :type: Expression
1942 """
1943 def __init__(self, mh, location, typ, n_lhs, n_lower, n_upper):
1944 # lobster-trace: LRM.Relation
1945 super().__init__(location, typ)
1946 assert isinstance(mh, Message_Handler)
1947 assert isinstance(n_lhs, Expression)
1948 assert isinstance(n_lower, Expression)
1949 assert isinstance(n_upper, Expression)
1950 self.n_lhs = n_lhs
1951 self.n_lower = n_lower
1952 self.n_upper = n_upper
1954 self.n_lhs.ensure_type(mh, Builtin_Numeric_Type)
1955 self.n_lower.ensure_type(mh, self.n_lhs.typ)
1956 self.n_upper.ensure_type(mh, self.n_lhs.typ)
1958 def to_string(self):
1959 return "%s in %s .. %s" % (self.n_lhs.to_string(),
1960 self.n_lower.to_string(),
1961 self.n_upper.to_string())
1963 def dump(self, indent=0): # pragma: no cover
1964 # lobster-exclude: Debugging feature
1965 self.write_indent(indent, "Range Test")
1966 self.write_indent(indent + 1, f"Type: {self.typ}")
1967 self.n_lhs.dump(indent + 1)
1968 self.n_lower.dump(indent + 1)
1969 self.n_upper.dump(indent + 1)
1971 def evaluate(self, mh, context, gstab):
1972 # lobster-trace: LRM.Null_Is_Invalid
1973 assert isinstance(mh, Message_Handler)
1974 assert context is None or isinstance(context, dict)
1975 assert gstab is None or isinstance(gstab, Symbol_Table)
1977 v_lhs = self.n_lhs.evaluate(mh, context, gstab)
1978 if v_lhs.value is None: 1978 ↛ 1979line 1978 didn't jump to line 1979 because the condition on line 1978 was never true
1979 mh.error(v_lhs.location,
1980 "lhs of range check %s (%s) see must not be null" %
1981 (self.to_string(),
1982 mh.cross_file_reference(self.location)))
1984 v_lower = self.n_lower.evaluate(mh, context, gstab)
1985 if v_lower.value is None: 1985 ↛ 1986line 1985 didn't jump to line 1986 because the condition on line 1985 was never true
1986 mh.error(v_lower.location,
1987 "lower bound of range check %s (%s) must not be null" %
1988 (self.to_string(),
1989 mh.cross_file_reference(self.location)))
1991 v_upper = self.n_upper.evaluate(mh, context, gstab)
1992 if v_upper.value is None: 1992 ↛ 1993line 1992 didn't jump to line 1993 because the condition on line 1992 was never true
1993 mh.error(v_upper.location,
1994 "upper bound of range check %s (%s) must not be null" %
1995 (self.to_string(),
1996 mh.cross_file_reference(self.location)))
1998 return Value(location = self.location,
1999 value = v_lower.value <= v_lhs.value <= v_upper.value,
2000 typ = self.typ)
2002 def can_be_null(self):
2003 return False
2005 def uses_field_access(self):
2006 return (self.n_lhs.uses_field_access() or
2007 self.n_lower.uses_field_access() or
2008 self.n_upper.uses_field_access())
2011class OneOf_Expression(Expression):
2012 """OneOf expression
2014 For example in::
2016 oneof(a, b, c)
2017 ^^^^^^^ choices
2019 :attribute choices: a list of boolean expressions to test
2020 :type: list[Expression]
2021 """
2022 def __init__(self, mh, location, typ, choices):
2023 # lobster-trace: LRM.Signature_OneOf
2024 super().__init__(location, typ)
2025 assert isinstance(typ, Builtin_Boolean)
2026 assert isinstance(mh, Message_Handler)
2027 assert isinstance(choices, list)
2028 assert all(isinstance(item, Expression)
2029 for item in choices)
2030 self.choices = choices
2032 for n_choice in choices:
2033 n_choice.ensure_type(mh, Builtin_Boolean)
2035 def to_string(self):
2036 return "oneof(%s)" % ", ".join(n_choice.to_string()
2037 for n_choice in self.choices)
2039 def dump(self, indent=0): # pragma: no cover
2040 # lobster-exclude: Debugging feature
2041 self.write_indent(indent, "OneOf Test")
2042 self.write_indent(indent + 1, f"Type: {self.typ}")
2043 for n_choice in self.choices:
2044 n_choice.dump(indent + 1)
2046 def evaluate(self, mh, context, gstab):
2047 # lobster-trace: LRM.OneOf_Semantics
2048 assert isinstance(mh, Message_Handler)
2049 assert context is None or isinstance(context, dict)
2050 assert gstab is None or isinstance(gstab, Symbol_Table)
2052 v_choices = [n_choice.evaluate(mh, context, gstab).value
2053 for n_choice in self.choices]
2055 return Value(location = self.location,
2056 value = v_choices.count(True) == 1,
2057 typ = self.typ)
2059 def can_be_null(self):
2060 return False
2062 def uses_field_access(self):
2063 return any(n_choice.uses_field_access() for n_choice in self.choices)
2066class Action(Node):
2067 """An if or elseif part inside a conditional expression
2069 Each :class:`Conditional_Expression` is made up of a sequence of
2070 Actions. For example here is a single expression with two
2071 Actions::
2073 (if x == 0 then "zero" elsif x == 1 then "one" else "lots")
2074 ^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^
2076 Note that the else part is not an action, it is an attribute of
2077 the :class:`Conditional_Expression` itself.
2079 :attribute kind: Either if or elseif
2080 :type: str
2082 :attribute n_cond: The boolean condition expression
2083 :type: Expression
2085 :attribute n_expr: The value if the condition evaluates to true
2086 :type: Expression
2088 """
2089 def __init__(self, mh, t_kind, n_condition, n_expression):
2090 # lobster-trace: LRM.Conditional_Expression
2091 assert isinstance(mh, Message_Handler)
2092 assert isinstance(t_kind, Token)
2093 assert t_kind.kind == "KEYWORD"
2094 assert t_kind.value in ("if", "elsif")
2095 assert isinstance(n_condition, Expression)
2096 assert isinstance(n_expression, Expression)
2097 super().__init__(t_kind.location)
2098 self.kind = t_kind.value
2099 self.n_cond = n_condition
2100 self.n_expr = n_expression
2101 # lobster-trace: LRM.Conditional_Expression_Types
2102 self.n_cond.ensure_type(mh, Builtin_Boolean)
2104 def dump(self, indent=0): # pragma: no cover
2105 # lobster-exclude: Debugging feature
2106 self.write_indent(indent, f"{self.kind.capitalize()} Action")
2107 self.write_indent(indent + 1, "Condition")
2108 self.n_cond.dump(indent + 2)
2109 self.write_indent(indent + 1, "Value")
2110 self.n_expr.dump(indent + 2)
2112 def to_string(self):
2113 return "%s %s then %s" % (self.kind,
2114 self.n_cond.to_string(),
2115 self.n_expr.to_string())
2118class Conditional_Expression(Expression):
2119 """A conditional expression
2121 Each :class:`Conditional_Expression` is made up of a sequence of
2122 one or more :class:`Action`. For example here is a single
2123 expression with two Actions::
2125 (if x == 0 then "zero" elsif x == 1 then "one" else "lots")
2126 ^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^
2128 The else expression is part of the conditional expression itself.
2130 A conditional expression will have at least one action (the if
2131 action), and all other actions will be elsif actions. The else
2132 expression is not optional and will always be present. The types
2133 of all actions and the else expression will match.
2135 :attribute actions: a list of Actions
2136 :type: list[Action]
2138 :attribute else_expr: the else expression
2139 :type: Expression
2141 """
2142 def __init__(self, location, if_action):
2143 # lobster-trace: LRM.Conditional_Expression
2144 assert isinstance(if_action, Action)
2145 assert if_action.kind == "if"
2146 super().__init__(location, if_action.n_expr.typ)
2147 self.actions = [if_action]
2148 self.else_expr = None
2150 def add_elsif(self, mh, n_action):
2151 # lobster-trace: LRM.Conditional_Expression
2152 # lobster-trace; LRM.Conditional_Expression_Types
2153 assert isinstance(mh, Message_Handler)
2154 assert isinstance(n_action, Action)
2155 assert n_action.kind == "elsif"
2157 n_action.n_expr.ensure_type(mh, self.typ)
2158 self.actions.append(n_action)
2160 def set_else_part(self, mh, n_expr):
2161 # lobster-trace: LRM.Conditional_Expression
2162 # lobster-trace; LRM.Conditional_Expression_Types
2163 assert isinstance(mh, Message_Handler)
2164 assert isinstance(n_expr, Expression)
2166 n_expr.ensure_type(mh, self.typ)
2167 self.else_expr = n_expr
2169 def dump(self, indent=0): # pragma: no cover
2170 # lobster-exclude: Debugging feature
2171 self.write_indent(indent, "Conditional expression")
2172 for action in self.actions:
2173 action.dump(indent + 1)
2174 self.write_indent(indent + 1, "Else")
2175 self.else_expr.dump(indent + 2)
2177 def to_string(self):
2178 rv = "(" + " ".join(action.to_string()
2179 for action in self.actions)
2180 rv += " else %s" % self.else_expr.to_string()
2181 rv += ")"
2182 return rv
2184 def evaluate(self, mh, context, gstab):
2185 # lobster-trace: LRM.Conditional_Expression_Else
2186 # lobster-trace: LRM.Conditional_Expression_Evaluation
2187 # lobster-trace: LRM.Null_Is_Invalid
2188 assert isinstance(mh, Message_Handler)
2189 assert context is None or isinstance(context, dict)
2190 assert gstab is None or isinstance(gstab, Symbol_Table)
2192 for action in self.actions:
2193 v_cond = action.n_cond.evaluate(mh, context, gstab)
2194 if v_cond.value is None: 2194 ↛ 2195line 2194 didn't jump to line 2195 because the condition on line 2194 was never true
2195 mh.error(v_cond.location,
2196 "condition of %s (%s) must not be null" %
2197 (action.to_string(),
2198 mh.cross_file_reference(self.location)))
2199 if v_cond.value:
2200 return action.n_expr.evaluate(mh, context, gstab)
2202 return self.else_expr.evaluate(mh, context, gstab)
2204 def can_be_null(self):
2205 if self.else_expr and self.else_expr.can_be_null():
2206 return True
2208 return any(action.n_expr.can_be_null()
2209 for action in self.actions)
2211 def uses_field_access(self):
2212 return (any(action.n_cond.uses_field_access() or
2213 action.n_expr.uses_field_access()
2214 for action in self.actions) or
2215 (self.else_expr is not None and
2216 self.else_expr.uses_field_access()))
2219class Quantified_Expression(Expression):
2220 """A quantified expression
2222 For example::
2224 (forall x in array_component => x > 0)
2225 ^4 ^1 ^2 ^^^^^3
2227 A quantified expression introduces and binds a
2228 :class:`Quantified_Variable` (see 1) from a specified source (see
2229 2). When the body (see 3) is evaluated, the name of 1 is bound to
2230 each component of the source in turn.
2232 :attribute n_var: The quantified variable (see 1)
2233 :type: Quantified_Variable
2235 :attribute n_source: The array to iterate over (see 2)
2236 :type: Name_Reference
2238 :attribute n_expr: The body of the quantifier (see 3)
2239 :type: Expression
2241 :attribute universal: True means forall, false means exists (see 4)
2242 :type: Boolean
2244 """
2245 def __init__(self, mh, location,
2246 typ,
2247 universal,
2248 n_variable,
2249 n_source,
2250 n_expr):
2251 # lobster-trace: LRM.Quantified_Expression
2252 # lobster-trace: LRM.Quantification_Type
2253 super().__init__(location, typ)
2254 assert isinstance(typ, Builtin_Boolean)
2255 assert isinstance(universal, bool)
2256 assert isinstance(n_variable, Quantified_Variable)
2257 assert isinstance(n_expr, Expression)
2258 assert isinstance(n_source, Name_Reference)
2259 self.universal = universal
2260 self.n_var = n_variable
2261 self.n_expr = n_expr
2262 self.n_source = n_source
2263 self.n_expr.ensure_type(mh, Builtin_Boolean)
2265 def dump(self, indent=0): # pragma: no cover
2266 # lobster-exclude: Debugging feature
2267 if self.universal:
2268 self.write_indent(indent, "Universal quantified expression")
2269 else:
2270 self.write_indent(indent, "Existential quantified expression")
2271 self.n_var.dump(indent + 1)
2272 self.n_expr.dump(indent + 1)
2274 def to_string(self):
2275 return "(%s %s in %s => %s)" % ("forall"
2276 if self.universal
2277 else "exists",
2278 self.n_var.name,
2279 self.n_source.to_string(),
2280 self.n_expr.to_string())
2282 def evaluate(self, mh, context, gstab):
2283 # lobster-trace: LRM.Null_Is_Invalid
2284 # lobster-trace: LRM.Universal_Quantification_Semantics
2285 # lobster-trace: LRM.Existential_Quantification_Semantics
2286 assert isinstance(mh, Message_Handler)
2287 assert context is None or isinstance(context, dict)
2288 assert gstab is None or isinstance(gstab, Symbol_Table)
2290 if context is None: 2290 ↛ 2291line 2290 didn't jump to line 2291 because the condition on line 2290 was never true
2291 new_ctx = {}
2292 else:
2293 new_ctx = copy(context)
2295 # This is going to be a bit tricky. We essentially eliminate
2296 # the quantifier and substitute; for the sake of making better
2297 # error messages.
2298 assert isinstance(self.n_source.entity, Composite_Component)
2299 array_values = context[self.n_source.entity.name]
2300 if isinstance(array_values, Implicit_Null):
2301 mh.error(array_values.location,
2302 "%s in quantified expression %s (%s) "
2303 "must not be null" %
2304 (self.n_source.to_string(),
2305 self.to_string(),
2306 mh.cross_file_reference(self.location)))
2307 else:
2308 assert isinstance(array_values, Array_Aggregate)
2310 rv = self.universal
2311 loc = self.location
2312 for binding in array_values.value:
2313 new_ctx[self.n_var.name] = binding
2314 result = self.n_expr.evaluate(mh, new_ctx, gstab)
2315 assert isinstance(result.value, bool)
2316 if self.universal and not result.value:
2317 rv = False
2318 loc = binding.location
2319 break
2320 elif not self.universal and result.value:
2321 rv = True
2322 loc = binding.location
2323 break
2325 return Value(location = loc,
2326 value = rv,
2327 typ = self.typ)
2329 def can_be_null(self):
2330 return False
2332 def uses_field_access(self):
2333 return (self.n_source.uses_field_access() or
2334 self.n_expr.uses_field_access())
2337##############################################################################
2338# AST Nodes (Entities)
2339##############################################################################
2341class Entity(Node, metaclass=ABCMeta):
2342 """Base class for all entities.
2344 An entity is a concrete object (with a name) for which we need to
2345 allocate memory. Examples of entities are types and record
2346 objects.
2348 :attribute name: unqualified name of the entity
2349 :type: str
2351 """
2352 def __init__(self, name, location):
2353 # lobster-trace: LRM.Described_Name_Equality
2354 super().__init__(location)
2355 assert isinstance(name, str)
2356 self.name = name
2359class Typed_Entity(Entity, metaclass=ABCMeta):
2360 """Base class for entities with a type.
2362 A typed entity is a concrete object (with a name and TRLC type)
2363 for which we need to allocate memory. Examples of typed entities
2364 are record objects and components.
2366 :attribute n_typ: type of the entity
2367 :type: Type
2369 """
2370 def __init__(self, name, location, n_typ):
2371 # lobster-exclude: Constructor only declares variables
2372 super().__init__(name, location)
2373 assert isinstance(n_typ, Type)
2374 self.n_typ = n_typ
2377class Quantified_Variable(Typed_Entity):
2378 """Variable used in quantified expression.
2380 A quantified expression declares and binds a variable, for which
2381 we need a named entity. For example in::
2383 (forall x in array => x > 1)
2384 ^
2386 We represent this first x as a :class:`Quantified_Variable`, the
2387 second x will be an ordinary :class:`Name_Reference`.
2389 :attribute typ: type of the variable (i.e. element type of the array)
2390 :type: Type
2392 """
2393 def dump(self, indent=0): # pragma: no cover
2394 # lobster-exclude: Debugging feature
2395 self.write_indent(indent, f"Quantified Variable {self.name}")
2396 self.n_typ.dump(indent + 1)
2399class Type(Entity, metaclass=ABCMeta):
2400 """Abstract base class for all types.
2402 """
2403 def perform_type_checks(self, mh, value, gstab):
2404 assert isinstance(mh, Message_Handler)
2405 assert isinstance(value, Expression)
2406 assert isinstance(gstab, Symbol_Table)
2407 return True
2409 def get_example_value(self):
2410 # lobster-exclude: utility method
2411 assert False
2414class Concrete_Type(Type, metaclass=ABCMeta):
2415 # lobster-trace: LRM.Type_Declarations
2416 """Abstract base class for all non-anonymous types.
2418 :attribute n_package: package where this type was declared
2419 :type: Package
2420 """
2421 def __init__(self, name, location, n_package):
2422 super().__init__(name, location)
2423 assert isinstance(n_package, Package)
2424 self.n_package = n_package
2426 def fully_qualified_name(self):
2427 """Return the FQN for this type (i.e. PACKAGE.NAME)
2429 :returns: the type's full name
2430 :rtype: str
2431 """
2432 return self.n_package.name + "." + self.name
2434 def __hash__(self):
2435 return hash((self.n_package.name, self.name))
2437 def __repr__(self):
2438 return "%s<%s>" % (self.__class__.__name__,
2439 self.fully_qualified_name())
2442class Builtin_Type(Type, metaclass=ABCMeta):
2443 # lobster-trace: LRM.Builtin_Types
2444 """Abstract base class for all builtin types.
2446 """
2447 LOCATION = Location(file_name = "<builtin>")
2449 def __init__(self, name):
2450 super().__init__(name, Builtin_Type.LOCATION)
2452 def dump(self, indent=0): # pragma: no cover
2453 self.write_indent(indent, self.__class__.__name__)
2456class Builtin_Numeric_Type(Builtin_Type, metaclass=ABCMeta):
2457 # lobster-trace: LRM.Builtin_Types
2458 """Abstract base class for all builtin numeric types.
2460 """
2461 def dump(self, indent=0): # pragma: no cover
2462 self.write_indent(indent, self.__class__.__name__)
2465class Builtin_Function(Entity):
2466 # lobster-trace: LRM.Builtin_Functions
2467 """Builtin functions.
2469 These are auto-generated by the :class:`~trlc.trlc.Source_Manager`.
2471 :attribute arity: number of parameters
2472 :type: int
2474 :attribute arity_at_least: when true, arity indicates a lower bound
2475 :type: bool
2477 """
2478 LOCATION = Location(file_name = "<builtin>")
2480 def __init__(self, name, arity, arity_at_least=False):
2481 super().__init__(name, Builtin_Function.LOCATION)
2482 assert isinstance(arity, int)
2483 assert isinstance(arity_at_least, bool)
2484 assert arity >= 0
2485 self.arity = arity
2486 self.arity_at_least = arity_at_least
2488 def dump(self, indent=0): # pragma: no cover
2489 self.write_indent(indent, self.__class__.__name__ + " " + self.name)
2492class Array_Type(Type):
2493 """Anonymous array type.
2495 These are declared implicitly for each record component that has
2496 an array specifier::
2498 foo Integer [5 .. *]
2499 ^
2501 :attribute lower_bound: minimum number of elements
2502 :type: int
2504 :attribute loc_lower: text location of the lower bound indicator
2505 :type: Location
2507 :attribute upper_bound: maximum number of elements (or None)
2508 :type: int
2510 :attribute loc_upper: text location of the upper bound indicator
2511 :type: Location
2513 :attribute element_type: type of the array elements
2514 :type: Type
2516 """
2517 def __init__(self,
2518 location,
2519 element_type,
2520 loc_lower,
2521 lower_bound,
2522 loc_upper,
2523 upper_bound):
2524 # lobster-exclude: Constructor only declares variables
2525 assert isinstance(element_type, Type) or element_type is None
2526 assert isinstance(lower_bound, int)
2527 assert lower_bound >= 0
2528 assert upper_bound is None or isinstance(upper_bound, int)
2529 assert upper_bound is None or upper_bound >= 0
2530 assert isinstance(loc_lower, Location)
2531 assert isinstance(loc_upper, Location)
2533 if element_type is None: 2533 ↛ 2534line 2533 didn't jump to line 2534 because the condition on line 2533 was never true
2534 name = "universal array"
2535 elif upper_bound is None:
2536 if lower_bound == 0:
2537 name = "array of %s" % element_type.name
2538 else:
2539 name = "array of at least %u %s" % (lower_bound,
2540 element_type.name)
2541 elif lower_bound == upper_bound:
2542 name = "array of %u %s" % (lower_bound,
2543 element_type.name)
2544 else:
2545 name = "array of %u to %u %s" % (lower_bound,
2546 upper_bound,
2547 element_type.name)
2548 super().__init__(name, location)
2549 self.lower_bound = lower_bound
2550 self.loc_lower = loc_lower
2551 self.upper_bound = upper_bound
2552 self.loc_upper = loc_upper
2553 self.element_type = element_type
2555 def dump(self, indent=0): # pragma: no cover
2556 # lobster-exclude: Debugging feature
2557 self.write_indent(indent, "Array_Type")
2558 self.write_indent(indent + 1, f"Lower bound: {self.lower_bound}")
2559 if self.upper_bound is None:
2560 self.write_indent(indent + 1, "Upper bound: *")
2561 else:
2562 self.write_indent(indent + 1, f"Upper bound: {self.upper_bound}")
2563 self.write_indent(indent + 1, f"Element type: {self.element_type.name}")
2565 def perform_type_checks(self, mh, value, gstab):
2566 assert isinstance(mh, Message_Handler)
2567 assert isinstance(gstab, Symbol_Table)
2569 if isinstance(value, Array_Aggregate):
2570 return all(self.element_type.perform_type_checks(mh, v, gstab)
2571 for v in value.value)
2572 else:
2573 assert isinstance(value, Implicit_Null)
2574 return True
2576 def get_example_value(self):
2577 # lobster-exclude: utility method
2578 return "[%s]" % self.element_type.get_example_value()
2581class Union_Type(Type):
2582 # lobster-trace: LRM.union_type
2583 # lobster-trace: LRM.Union_Type_Minimum_Members
2584 # lobster-trace: LRM.Union_Type_Record_Types_Only
2585 """Anonymous union type for record references.
2587 These are declared implicitly when a record component specifies
2588 multiple allowed record types using bracket syntax::
2590 parent [Systemrequirement, Codebeamerrequirement]
2591 ^
2593 :attribute types: the allowed record types
2594 :type: list[Record_Type]
2596 """
2597 def __init__(self, location, types):
2598 assert isinstance(types, list)
2599 assert len(types) >= 1
2600 assert all(isinstance(t, Record_Type) for t in types)
2601 name = "[%s]" % ", ".join(t.name for t in types)
2602 super().__init__(name, location)
2603 self.types = types
2604 self._field_map = None
2606 def get_field_map(self):
2607 # lobster-trace: LRM.Union_Type_Field_Access
2608 """Compute accessible fields across all union members.
2610 Returns a dict mapping field name to a dict with keys:
2612 * ``component``: a representative Composite_Component
2613 * ``n_typ``: the field type (None if conflicting)
2614 * ``count``: how many member types have this field
2615 * ``total``: total number of member types
2616 * ``optional_in_any``: True if optional in at least one member
2618 :rtype: dict[str, dict]
2619 """
2620 if self._field_map is not None:
2621 return self._field_map
2623 field_map = {}
2624 for record_type in self.types:
2625 seen_in_type = set()
2626 for comp in record_type.all_components():
2627 if comp.name in seen_in_type: 2627 ↛ 2628line 2627 didn't jump to line 2628 because the condition on line 2627 was never true
2628 continue
2629 seen_in_type.add(comp.name)
2630 if comp.name not in field_map:
2631 field_map[comp.name] = {
2632 "component" : comp,
2633 "n_typ" : comp.n_typ,
2634 "count" : 1,
2635 "total" : len(self.types),
2636 "optional_in_any" : comp.optional,
2637 }
2638 else:
2639 info = field_map[comp.name]
2640 info["count"] += 1
2641 # Type identity (is) is correct here:
2642 # non-union type objects are structural
2643 # singletons in the symbol table, so
2644 # identity comparison is both correct and
2645 # cheap.
2646 if info["n_typ"] is not comp.n_typ:
2647 info["n_typ"] = None # type conflict
2648 if comp.optional: 2648 ↛ 2649line 2648 didn't jump to line 2649 because the condition on line 2648 was never true
2649 info["optional_in_any"] = True
2651 self._field_map = field_map
2652 return self._field_map
2654 def dump(self, indent=0): # pragma: no cover
2655 # lobster-exclude: Debugging feature
2656 self.write_indent(indent, "Union_Type")
2657 for t in self.types:
2658 self.write_indent(indent + 1, t.name)
2660 def perform_type_checks(self, mh, value, gstab):
2661 # Union types have no checks of their own; type validation
2662 # happens in Record_Reference.resolve_references() via
2663 # is_compatible(). Returning True unconditionally is
2664 # intentional.
2665 assert isinstance(mh, Message_Handler)
2666 assert isinstance(value, Expression)
2667 assert isinstance(gstab, Symbol_Table)
2668 return True
2670 def is_compatible(self, record_type):
2671 """Test if the given record type is accepted by this union.
2673 :param record_type: type to check
2674 :type record_type: Record_Type
2676 :returns: true if the type is or extends one of the union members
2677 :rtype: bool
2678 """
2679 assert isinstance(record_type, Record_Type)
2680 return any(record_type.is_subclass_of(t) for t in self.types)
2682 def get_example_value(self):
2683 # lobster-exclude: utility method
2684 return "%s_instance" % self.types[0].name
2687class Builtin_Integer(Builtin_Numeric_Type):
2688 # lobster-trace: LRM.Builtin_Types
2689 # lobster-trace: LRM.Integer_Values
2690 """Builtin integer type."""
2691 def __init__(self):
2692 super().__init__("Integer")
2694 def get_example_value(self):
2695 # lobster-exclude: utility method
2696 return "100"
2699class Builtin_Decimal(Builtin_Numeric_Type):
2700 # lobster-trace: LRM.Builtin_Types
2701 # lobster-trace: LRM.Decimal_Values
2702 """Builtin decimal type."""
2703 def __init__(self):
2704 super().__init__("Decimal")
2706 def get_example_value(self):
2707 # lobster-exclude: utility method
2708 return "3.14"
2711class Builtin_Boolean(Builtin_Type):
2712 # lobster-trace: LRM.Builtin_Types
2713 # lobster-trace: LRM.Boolean_Values
2714 """Builtin boolean type."""
2715 def __init__(self):
2716 super().__init__("Boolean")
2718 def get_example_value(self):
2719 # lobster-exclude: utility method
2720 return "true"
2723class Builtin_String(Builtin_Type):
2724 # lobster-trace: LRM.Builtin_Types
2725 # lobster-trace: LRM.String_Values
2726 """Builtin string type."""
2727 def __init__(self):
2728 super().__init__("String")
2730 def get_example_value(self):
2731 # lobster-exclude: utility method
2732 return "\"potato\""
2735class Builtin_Markup_String(Builtin_String):
2736 # lobster-trace: LRM.Builtin_Types
2737 # lobster-trace: LRM.Markup_String_Values
2738 """Builtin string type that allows checked references to TRLC
2739 objects.
2740 """
2741 def __init__(self):
2742 super().__init__()
2743 self.name = "Markup_String"
2745 def get_example_value(self):
2746 # lobster-exclude: utility method
2747 return "\"also see [[potato]]\""
2750class Package(Entity):
2751 """Packages.
2753 A package is declared when it is first encountered (in either a
2754 rsl or trlc file). A package contains all symbols declared in it,
2755 both types and record objects. A package is not associated with
2756 just a single file, it can be spread over multiple files.
2758 :attribute declared_late: indicates if this package is declared in a \
2759 trlc file
2760 :type: bool
2762 :attribute symbols: symbol table of the package
2763 :type: Symbol_Table
2765 """
2766 def __init__(self, name, location, builtin_stab, declared_late):
2767 # lobster-exclude: Constructor only declares variables
2768 super().__init__(name, location)
2769 assert isinstance(builtin_stab, Symbol_Table)
2770 assert isinstance(declared_late, bool)
2771 self.symbols = Symbol_Table()
2772 self.symbols.make_visible(builtin_stab)
2773 self.declared_late = declared_late
2775 def dump(self, indent=0): # pragma: no cover
2776 # lobster-exclude: Debugging feature
2777 self.write_indent(indent, f"Package {self.name}")
2778 self.write_indent(indent + 1, f"Declared_Late: {self.declared_late}")
2779 self.symbols.dump(indent + 1, omit_heading=True)
2781 def __repr__(self):
2782 return "%s<%s>" % (self.__class__.__name__,
2783 self.name)
2786class Composite_Type(Concrete_Type, metaclass=ABCMeta):
2787 """Abstract base for record and tuple types, as they share some
2788 functionality.
2790 :attribute components: type components (including inherited if applicable)
2791 :type: Symbol_Table[Composite_Component]
2793 :attribute description: user-supplied description of the type or None
2794 :type: str
2796 :attribute checks: user-defined checks for this type (excluding \
2797 inherited checks)
2798 :type: list[Check]
2800 """
2801 def __init__(self,
2802 name,
2803 description,
2804 location,
2805 package,
2806 inherited_symbols=None):
2807 # lobster-trace: LRM.Described_Name_Description
2808 super().__init__(name, location, package)
2809 assert isinstance(description, str) or description is None
2810 assert isinstance(inherited_symbols, Symbol_Table) or \
2811 inherited_symbols is None
2813 self.components = Symbol_Table(inherited_symbols)
2814 self.description = description
2815 self.checks = []
2817 def add_check(self, n_check):
2818 # lobster-trace: LRM.Check_Evaluation_Order
2819 assert isinstance(n_check, Check)
2820 self.checks.append(n_check)
2822 def iter_checks(self):
2823 # lobster-trace: LRM.Check_Evaluation_Order
2824 yield from self.checks
2826 def all_components(self):
2827 # lobster-exclude: Convenience function
2828 """Convenience function to get a list of all components.
2830 :rtype: list[Composite_Component]
2831 """
2832 return list(self.components.table.values())
2835class Composite_Component(Typed_Entity):
2836 """Component in a record or tuple.
2838 When declaring a composite type, for each component an entity is
2839 declared::
2841 type|tuple T {
2842 foo "blah" optional Boolean
2843 ^1 ^2 ^3 ^4
2845 :attribute description: optional text (see 2) for this component, or None
2846 :type: str
2848 :attribute member_of: a link back to the containing record or tuple; \
2849 for inherited fields this refers back to the original base record type
2850 :type: Composite_Type
2852 :attribute optional: indicates if the component can be null or not (see 3)
2853 :type: bool
2855 """
2857 def __init__(self,
2858 name,
2859 description,
2860 location,
2861 member_of,
2862 n_typ,
2863 optional):
2864 # lobster-trace: LRM.Described_Name_Description
2865 super().__init__(name, location, n_typ)
2866 assert isinstance(description, str) or description is None
2867 assert isinstance(member_of, Composite_Type)
2868 assert isinstance(optional, bool)
2869 self.description = description
2870 self.member_of = member_of
2871 self.optional = optional
2873 def dump(self, indent=0): # pragma: no cover
2874 # lobster-exclude: Debugging feature
2875 self.write_indent(indent, f"Composite_Component {self.name}")
2876 if self.description:
2877 self.write_indent(indent + 1, f"Description: {self.description}")
2878 self.write_indent(indent + 1, f"Optional: {self.optional}")
2879 self.write_indent(indent + 1, f"Type: {self.n_typ.name}")
2881 def __repr__(self):
2882 return "%s<%s>" % (self.__class__.__name__,
2883 self.member_of.fully_qualified_name() + "." +
2884 self.name)
2887class Record_Type(Composite_Type):
2888 """A user-defined record type.
2890 In this example::
2892 type T "optional description of T" extends Root_T {
2893 ^1 ^2 ^3
2895 Note that (1) is part of the :class:`Entity` base, and (2) is part
2896 of the :class:`Composite_Type` base.
2898 :attribute parent: root type or None, indicated by (3) above
2899 :type: Record_Type
2901 :attribute frozen: mapping of frozen components
2902 :type: dict[str, Expression]
2904 :attribute is_final: type is final (i.e. no new components may be declared)
2905 :type: bool
2907 :attribute is_abstract: type is abstract
2908 :type: bool
2910 """
2911 def __init__(self,
2912 name,
2913 description,
2914 location,
2915 package,
2916 n_parent,
2917 is_abstract):
2918 # lobster-exclude: Constructor only declares variables
2919 assert isinstance(n_parent, Record_Type) or n_parent is None
2920 assert isinstance(is_abstract, bool)
2921 super().__init__(name,
2922 description,
2923 location,
2924 package,
2925 n_parent.components if n_parent else None)
2926 self.parent = n_parent
2927 self.frozen = {}
2928 self.is_final = (n_parent.is_final if n_parent else False)
2929 self.is_abstract = is_abstract
2931 def iter_checks(self):
2932 # lobster-trace: LRM.Check_Evaluation_Order
2933 # lobster-trace: LRM.Check_Evaluation_Order_For_Extensions
2934 if self.parent:
2935 yield from self.parent.iter_checks()
2936 yield from self.checks
2938 def dump(self, indent=0): # pragma: no cover
2939 # lobster-exclude: Debugging feature
2940 self.write_indent(indent, f"Record_Type {self.name}")
2941 if self.description:
2942 self.write_indent(indent + 1, f"Description: {self.description}")
2943 if self.parent:
2944 self.write_indent(indent + 1, f"Parent: {self.parent.name}")
2945 self.components.dump(indent + 1, omit_heading=True)
2946 if self.checks:
2947 self.write_indent(indent + 1, "Checks")
2948 for n_check in self.checks:
2949 n_check.dump(indent + 2)
2950 else:
2951 self.write_indent(indent + 1, "Checks: None")
2953 def all_components(self):
2954 """Convenience function to get a list of all components.
2956 :rtype: list[Composite_Component]
2957 """
2958 if self.parent:
2959 return self.parent.all_components() + \
2960 list(self.components.table.values())
2961 else:
2962 return list(self.components.table.values())
2964 def is_subclass_of(self, record_type):
2965 """ Checks if this record type is or inherits from the given type
2967 :param record_type: check if are or extend this type
2968 :type record_type: Record_Type
2970 :returns: true if we are or extend the given type
2971 :rtype: Boolean
2972 """
2973 assert isinstance(record_type, Record_Type)
2975 ptr = self
2976 while ptr:
2977 if ptr is record_type:
2978 return True
2979 else:
2980 ptr = ptr.parent
2981 return False
2983 def is_frozen(self, n_component):
2984 """Test if the given component is frozen.
2986 :param n_component: a composite component of this record type \
2987 (or any of its parents)
2988 :type n_component: Composite_Component
2990 :rtype: bool
2991 """
2992 assert isinstance(n_component, Composite_Component)
2993 if n_component.name in self.frozen:
2994 return True
2995 elif self.parent:
2996 return self.parent.is_frozen(n_component)
2997 else:
2998 return False
3000 def get_freezing_expression(self, n_component):
3001 """Retrieve the frozen value for a frozen component
3003 It is an internal compiler error to call this method with a
3004 component that his not frozen.
3006 :param n_component: a frozen component of this record type \
3007 (or any of its parents)
3008 :type n_component: Composite_Component
3010 :rtype: Expression
3012 """
3013 assert isinstance(n_component, Composite_Component)
3014 if n_component.name in self.frozen: 3014 ↛ 3016line 3014 didn't jump to line 3016 because the condition on line 3014 was always true
3015 return self.frozen[n_component.name]
3016 elif self.parent:
3017 return self.parent.get_freezing_expression(n_component)
3018 else:
3019 assert False
3021 def get_example_value(self):
3022 # lobster-exclude: utility method
3023 return "%s_instance" % self.name
3026class Tuple_Type(Composite_Type):
3027 """A user-defined tuple type.
3029 In this example::
3031 tuple T "optional description of T" {
3032 ^1 ^2
3034 Note that (1) is part of the :class:`Entity` base, and (2) is part
3035 of the :class:`Composite_Type` base.
3037 :attribute separators: list of syntactic separators.
3038 :type: list[Separator]
3040 Note the list of separators will either be empty, or there will be
3041 precisely one less separator than components.
3043 """
3044 def __init__(self, name, description, location, package):
3045 # lobster-trace: LRM.Tuple_Declaration
3046 super().__init__(name,
3047 description,
3048 location,
3049 package)
3050 self.separators = []
3052 def add_separator(self, n_separator):
3053 # lobster-exclude: utility method
3054 assert isinstance(n_separator, Separator)
3055 assert len(self.separators) + 1 == len(self.components.table)
3056 self.separators.append(n_separator)
3058 def iter_separators(self):
3059 """Iterate over all separators"""
3060 # lobster-exclude: utility method
3061 yield from self.separators
3063 def iter_sequence(self):
3064 """Iterate over all components and separators in syntactic order"""
3065 # lobster-exclude: utility method
3066 if self.separators:
3067 for i, n_component in enumerate(self.components.table.values()):
3068 yield n_component
3069 if i < len(self.separators):
3070 yield self.separators[i]
3071 else:
3072 yield from self.components.table.values()
3074 def has_separators(self):
3075 """Returns true if a tuple type requires separators"""
3076 # lobster-exclude: utility method
3077 return bool(self.separators)
3079 def dump(self, indent=0): # pragma: no cover
3080 # lobster-exclude: Debugging feature
3081 self.write_indent(indent, f"Tuple_Type {self.name}")
3082 if self.description:
3083 self.write_indent(indent + 1, f"Description: {self.description}")
3084 self.write_indent(indent + 1, "Fields")
3085 for n_item in self.iter_sequence():
3086 n_item.dump(indent + 2)
3087 if self.checks:
3088 self.write_indent(indent + 1, "Checks")
3089 for n_check in self.checks:
3090 n_check.dump(indent + 2)
3091 else:
3092 self.write_indent(indent + 1, "Checks: None")
3094 def perform_type_checks(self, mh, value, gstab):
3095 # lobster-trace: LRM.Check_Evaluation_Order
3096 assert isinstance(mh, Message_Handler)
3097 assert isinstance(gstab, Symbol_Table)
3099 if isinstance(value, Tuple_Aggregate): 3099 ↛ 3106line 3099 didn't jump to line 3106 because the condition on line 3099 was always true
3100 ok = True
3101 for check in self.iter_checks():
3102 if not check.perform(mh, value, gstab):
3103 ok = False
3104 return ok
3105 else:
3106 assert isinstance(value, Implicit_Null)
3107 return True
3109 def get_example_value(self):
3110 # lobster-exclude: utility method
3111 parts = []
3112 for n_item in self.iter_sequence():
3113 if isinstance(n_item, Composite_Component):
3114 parts.append(n_item.n_typ.get_example_value())
3115 else:
3116 parts.append(n_item.to_string())
3117 if self.has_separators():
3118 return " ".join(parts)
3119 else:
3120 return "(%s)" % ", ".join(parts)
3123class Separator(Node):
3124 # lobster-trace: LRM.Tuple_Declaration
3125 """User-defined syntactic separator
3127 For example::
3129 separator x
3130 ^1
3132 :attribute token: token used to separate fields of the tuple
3133 :type: Token
3134 """
3135 def __init__(self, token):
3136 super().__init__(token.location)
3137 assert isinstance(token, Token) and token.kind in ("IDENTIFIER",
3138 "AT",
3139 "COLON",
3140 "SEMICOLON")
3141 self.token = token
3143 def to_string(self):
3144 return {
3145 "AT" : "@",
3146 "COLON" : ":",
3147 "SEMICOLON" : ";"
3148 }.get(self.token.kind, self.token.value)
3150 def dump(self, indent=0): # pragma: no cover
3151 self.write_indent(indent, f"Separator {self.token.value}")
3154class Enumeration_Type(Concrete_Type):
3155 """User-defined enumeration types.
3157 For example::
3159 enum T "potato" {
3160 ^1 ^2
3162 :attribute description: user supplied optional description, or None
3163 :type: str
3165 :attribute literals: the literals in this enumeration
3166 :type: Symbol_Table[Enumeration_Literal_Spec]
3168 """
3169 def __init__(self, name, description, location, package):
3170 # lobster-trace: LRM.Described_Name_Description
3171 super().__init__(name, location, package)
3172 assert isinstance(description, str) or description is None
3173 self.literals = Symbol_Table()
3174 self.description = description
3176 def dump(self, indent=0): # pragma: no cover
3177 # lobster-exclude: Debugging feature
3178 self.write_indent(indent, f"Enumeration_Type {self.name}")
3179 if self.description:
3180 self.write_indent(indent + 1, f"Description: {self.description}")
3181 self.literals.dump(indent + 1, omit_heading=True)
3183 def get_example_value(self):
3184 # lobster-exclude: utility method
3185 options = list(self.literals.values())
3186 if options:
3187 choice = len(options) // 2
3188 return self.name + "." + choice.name
3189 else:
3190 return "ERROR"
3193class Enumeration_Literal_Spec(Typed_Entity):
3194 """Declared literal in an enumeration declaration.
3196 Note that for literals mentioned later in record object
3197 declarations, we use :class:`Enumeration_Literal`. Literal specs
3198 are used here::
3200 enum ASIL {
3201 QM "not safety related"
3202 ^1 ^2
3204 :attribute description: the optional user-supplied description, or None
3205 :type: str
3207 """
3208 def __init__(self, name, description, location, enum):
3209 # lobster-trace: LRM.Described_Name_Description
3210 super().__init__(name, location, enum)
3211 assert isinstance(description, str) or description is None
3212 assert isinstance(enum, Enumeration_Type)
3213 self.description = description
3215 def dump(self, indent=0): # pragma: no cover
3216 # lobster-exclude: Debugging feature
3217 self.write_indent(indent, f"Enumeration_Literal_Spec {self.name}")
3218 if self.description:
3219 self.write_indent(indent + 1, f"Description: {self.description}")
3222class Record_Object(Typed_Entity):
3223 """A declared instance of a record type.
3225 This is going to be the bulk of all entities created by TRLC::
3227 section "Potato" {
3228 ^5
3229 Requirement PotatoReq {
3230 ^1 ^2
3231 component1 = 42
3232 ^3 ^4
3234 Note that the name (see 2) and type (see 1) of the object is
3235 provided by the name attribute of the :class:`Typed_Entity` base
3236 class.
3238 :attribute field: the specific values for all components (see 3 and 4)
3239 :type: dict[str, Expression]
3241 :attribute section: None or the section this record is contained in (see 5)
3242 :type: Section
3244 :attribute n_package: The package in which this record is declared in
3245 :type: Section
3247 The actual type of expressions in the field attribute are limited
3248 to:
3250 * :class:`Literal`
3251 * :class:`Unary_Expression`
3252 * :class:`Array_Aggregate`
3253 * :class:`Tuple_Aggregate`
3254 * :class:`Record_Reference`
3255 * :class:`Implicit_Null`
3257 """
3258 def __init__(self, name, location, n_typ, section, n_package):
3259 # lobster-trace: LRM.Section_Declaration
3260 # lobster-trace: LRM.Unspecified_Optional_Components
3261 # lobster-trace: LRM.Record_Object_Declaration
3263 assert isinstance(n_typ, Record_Type)
3264 assert isinstance(section, list) or section is None
3265 assert isinstance(n_package, Package)
3266 super().__init__(name, location, n_typ)
3267 self.field = {
3268 comp.name: Implicit_Null(self, comp)
3269 for comp in self.n_typ.all_components()
3270 }
3271 self.section = section
3272 self.n_package = n_package
3274 def fully_qualified_name(self):
3275 """Return the FQN for this type (i.e. PACKAGE.NAME)
3277 :returns: the object's full name
3278 :rtype: str
3279 """
3280 return self.n_package.name + "." + self.name
3282 def to_python_dict(self):
3283 """Return an evaluated and simplified object for Python.
3285 For example it might provide::
3287 {"foo" : [1, 2, 3],
3288 "bar" : None,
3289 "baz" : "value"}
3291 This is a function especially designed for the Python API. The
3292 name of the object itself is not in this returned dictionary.
3294 """
3295 return {name: value.to_python_object()
3296 for name, value in self.field.items()}
3298 def is_component_implicit_null(self, component) -> bool:
3299 return not isinstance(self.field[component.name], Implicit_Null)
3301 def assign(self, component, value):
3302 assert isinstance(component, Composite_Component)
3303 assert isinstance(value, (Literal,
3304 Array_Aggregate,
3305 Tuple_Aggregate,
3306 Record_Reference,
3307 Implicit_Null,
3308 Unary_Expression)), \
3309 "value is %s" % value.__class__.__name__
3310 if self.is_component_implicit_null(component): 3310 ↛ 3311line 3310 didn't jump to line 3311 because the condition on line 3310 was never true
3311 raise KeyError(f"Component {component.name} already \
3312 assigned to {self.n_typ.name} {self.name}!")
3313 self.field[component.name] = value
3315 def dump(self, indent=0): # pragma: no cover
3316 # lobster-exclude: Debugging feature
3317 self.write_indent(indent, f"Record_Object {self.name}")
3318 self.write_indent(indent + 1, f"Type: {self.n_typ.name}")
3319 for key, value in self.field.items():
3320 self.write_indent(indent + 1, f"Field {key}")
3321 value.dump(indent + 2)
3322 if self.section:
3323 self.section[-1].dump(indent + 1)
3325 def resolve_references(self, mh):
3326 assert isinstance(mh, Message_Handler)
3327 for val in self.field.values():
3328 val.resolve_references(mh)
3330 def perform_checks(self, mh, gstab):
3331 # lobster-trace: LRM.Check_Evaluation_Order
3332 # lobster-trace: LRM.Evaluation_Of_Checks
3333 assert isinstance(mh, Message_Handler)
3334 assert isinstance(gstab, Symbol_Table)
3336 ok = True
3338 # First evaluate all tuple checks
3339 for n_comp in self.n_typ.all_components():
3340 if not n_comp.n_typ.perform_type_checks(mh,
3341 self.field[n_comp.name],
3342 gstab):
3343 ok = False
3345 # TODO: Is there a bug here (a check relies on a tuple check)?
3347 # Then evaluate all record checks
3348 for check in self.n_typ.iter_checks():
3349 # Prints messages, if applicable. Raises exception on
3350 # fatal checks, which causes this to abort.
3351 if not check.perform(mh, self, gstab):
3352 ok = False
3354 return ok
3356 def __repr__(self):
3357 return "%s<%s>" % (self.__class__.__name__,
3358 self.n_package.name + "." +
3359 self.n_typ.name + "." +
3360 self.name)
3363class Section(Entity):
3364 # lobster-trace: LRM.Section_Declaration
3365 """A section for readability
3367 This represents a section construct in TRLC files to group record
3368 objects together::
3370 section "Foo" {
3371 ^^^^^ parent section
3372 section "Bar" {
3373 ^^^^^ section
3375 :attribute parent: the parent section or None
3376 :type: Section
3378 """
3379 def __init__(self, name, location, parent):
3380 super().__init__(name, location)
3381 assert isinstance(parent, Section) or parent is None
3382 self.parent = parent
3384 def dump(self, indent=0): # pragma: no cover
3385 self.write_indent(indent, f"Section {self.name}")
3386 if self.parent is None:
3387 self.write_indent(indent + 1, "Parent: None")
3388 else:
3389 self.write_indent(indent + 1, f"Parent: {self.parent.name}")
3392##############################################################################
3393# Symbol Table & Scopes
3394##############################################################################
3396class Symbol_Table:
3397 """ Symbol table mapping names to entities
3398 """
3399 def __init__(self, parent=None):
3400 # lobster-exclude: Constructor only declares variables
3401 assert isinstance(parent, Symbol_Table) or parent is None
3402 self.parent = parent
3403 self.imported = []
3404 self.table = OrderedDict()
3405 self.trlc_files = []
3406 self.section_names = []
3408 @staticmethod
3409 def simplified_name(name):
3410 # lobster-trace: LRM.Sufficiently_Distinct
3411 assert isinstance(name, str)
3412 return name.lower().replace("_", "")
3414 def all_names(self):
3415 # lobster-exclude: API for users
3416 """ All names in the symbol table
3418 :rtype: set[str]
3419 """
3420 rv = set(item.name for item in self.table.values())
3421 if self.parent:
3422 rv |= self.parent.all_names()
3423 return rv
3425 def iter_record_objects_by_section(self):
3426 """API for users
3428 Retriving information about the section hierarchy for record objects
3429 Inputs: folder with trlc files where trlc files have sections,
3430 sub sections and record objects
3431 Output: Information about sections and level of sections,
3432 record objects and levels of record object
3433 """
3434 for record_object in self.iter_record_objects():
3435 location = record_object.location.file_name
3436 if location not in self.trlc_files: 3436 ↛ 3439line 3436 didn't jump to line 3439 because the condition on line 3436 was always true
3437 self.trlc_files.append(location)
3438 yield location
3439 if record_object.section:
3440 object_level = len(record_object.section) - 1
3441 for level, section in enumerate(record_object.section):
3442 if section not in self.section_names: 3442 ↛ 3441line 3442 didn't jump to line 3441 because the condition on line 3442 was always true
3443 self.section_names.append(section)
3444 yield section.name, level
3445 yield record_object, object_level
3446 else:
3447 object_level = 0
3448 yield record_object, object_level
3450 def iter_record_objects(self):
3451 # lobster-exclude: API for users
3452 """ Iterate over all record objects
3454 :rtype: iterable[Record_Object]
3455 """
3456 for item in self.table.values():
3457 if isinstance(item, Package):
3458 yield from item.symbols.iter_record_objects()
3460 elif isinstance(item, Record_Object):
3461 yield item
3463 def values(self, subtype=None):
3464 # lobster-exclude: API for users
3465 assert subtype is None or isinstance(subtype, type)
3466 if self.parent:
3467 yield from self.parent.values(subtype)
3468 for name in sorted(self.table):
3469 if subtype is None or isinstance(self.table[name], subtype):
3470 yield self.table[name]
3472 def make_visible(self, stab):
3473 assert isinstance(stab, Symbol_Table)
3474 self.imported.append(stab)
3476 def register(self, mh, entity):
3477 # lobster-trace: LRM.Duplicate_Types
3478 # lobster-trace: LRM.Unique_Enumeration_Literals
3479 # lobster-trace: LRM.Tuple_Unique_Field_Names
3480 # lobster-trace: LRM.Sufficiently_Distinct
3481 # lobster-trace: LRM.Unique_Object_Names
3483 assert isinstance(mh, Message_Handler)
3484 assert isinstance(entity, Entity)
3486 simple_name = self.simplified_name(entity.name)
3488 if self.contains_raw(simple_name):
3489 pdef = self.lookup_direct(mh, entity.name, entity.location,
3490 simplified=True)
3491 if pdef.name == entity.name:
3492 mh.error(entity.location,
3493 "duplicate definition, previous definition at %s" %
3494 mh.cross_file_reference(pdef.location))
3495 else:
3496 mh.error(entity.location,
3497 "%s is too similar to %s, declared at %s" %
3498 (entity.name,
3499 pdef.name,
3500 mh.cross_file_reference(pdef.location)))
3502 else:
3503 self.table[simple_name] = entity
3505 def __contains__(self, name):
3506 # lobster-trace: LRM.Described_Name_Equality
3507 return self.contains(name)
3509 def contains_raw(self, simple_name, precise_name=None):
3510 # lobster-trace: LRM.Described_Name_Equality
3511 # lobster-trace: LRM.Sufficiently_Distinct
3512 #
3513 # Internal function to test if the simplified name is in the
3514 # table.
3515 assert isinstance(simple_name, str)
3516 assert isinstance(precise_name, str) or precise_name is None
3518 if simple_name in self.table:
3519 # No need to continue searching since registering a
3520 # clashing name would have been stopped
3521 return precise_name is None or \
3522 self.table[simple_name].name == precise_name
3524 elif self.parent:
3525 return self.parent.contains_raw(simple_name, precise_name)
3527 for stab in self.imported:
3528 if stab.contains_raw(simple_name, precise_name):
3529 return True
3531 return False
3533 def contains(self, name):
3534 # lobster-trace: LRM.Described_Name_Equality
3535 """ Tests if the given name is in the table
3537 :param name: the name to test
3538 :type name: str
3540 :rtype: bool
3541 """
3542 assert isinstance(name, str)
3543 return self.contains_raw(self.simplified_name(name), name)
3545 def lookup_assuming(self, mh, name, required_subclass=None):
3546 # lobster-trace: LRM.Described_Name_Equality
3547 # lobster-trace: LRM.Sufficiently_Distinct
3548 """Retrieve an object from the table assuming its there
3550 This is intended for the API specifically where you want to
3551 e.g. find some user-defined types you know are there.
3553 :param mh: The message handler to use
3554 :type mh: Message_Handler
3556 :param name: The name to search for
3557 :type name: str
3559 :param required_subclass: If set, creates an error if the object \
3560 is not an instance of the given class
3561 :type required_subclass: type
3563 :raise TRLC_Error: if the object is not of the required subclass
3564 :returns: the specified entity (or None if it does not exist)
3565 :rtype: Entity
3567 """
3568 assert isinstance(mh, Message_Handler)
3569 assert isinstance(name, str)
3570 assert isinstance(required_subclass, type) or required_subclass is None
3572 simple_name = self.simplified_name(name)
3574 ptr = self
3575 for ptr in [self] + self.imported: 3575 ↛ 3593line 3575 didn't jump to line 3593 because the loop on line 3575 didn't complete
3576 while ptr: 3576 ↛ 3575line 3576 didn't jump to line 3575 because the condition on line 3576 was always true
3577 if simple_name in ptr.table: 3577 ↛ 3591line 3577 didn't jump to line 3591 because the condition on line 3577 was always true
3578 rv = ptr.table[simple_name]
3579 if rv.name != name: 3579 ↛ 3580line 3579 didn't jump to line 3580 because the condition on line 3579 was never true
3580 return None
3582 if required_subclass is not None and \ 3582 ↛ 3584line 3582 didn't jump to line 3584 because the condition on line 3582 was never true
3583 not isinstance(rv, required_subclass):
3584 mh.error(rv.location,
3585 "%s %s is not a %s" %
3586 (rv.__class__.__name__,
3587 name,
3588 required_subclass.__name__))
3589 return rv
3590 else:
3591 ptr = ptr.parent
3593 return None
3595 def lookup_direct(self,
3596 mh,
3597 name,
3598 error_location,
3599 required_subclass=None,
3600 simplified=False):
3601 # lobster-trace: LRM.Described_Name_Equality
3602 # lobster-trace: LRM.Sufficiently_Distinct
3603 # lobster-trace: LRM.Valid_Base_Names
3604 # lobster-trace: LRM.Valid_Access_Prefixes
3605 # lobster-trace: LRM.Valid_Function_Prefixes
3606 """Retrieve an object from the table
3608 For example::
3610 pkg = stab.lookup_direct(mh,
3611 "potato",
3612 Location("foobar.txt", 42),
3613 Package)
3615 This would search for an object named ``potato``. If it is
3616 found, and it is a package, it is returned. If it is not a
3617 Package, then the following error is issued::
3619 foobar.txt:42: error: Enumeration_Type potato is not a Package
3621 If it is not found at all, then the following error is issued::
3623 foobar.txt:42: error: unknown symbol potato
3625 :param mh: The message handler to use
3626 :type mh: Message_Handler
3628 :param name: The name to search for
3629 :type name: str
3631 :param error_location: Where to create the error if the name is \
3632 not found
3633 :type error_location: Location
3635 :param required_subclass: If set, creates an error if the object \
3636 is not an instance of the given class
3637 :type required_subclass: type
3639 :param simplified: If set, look up the given simplified name instead \
3640 of the actual name
3641 :type simplified: bool
3643 :raise TRLC_Error: if the name is not in the table
3644 :raise TRLC_Error: if the object is not of the required subclass
3645 :returns: the specified entity
3646 :rtype: Entity
3648 """
3649 assert isinstance(mh, Message_Handler)
3650 assert isinstance(name, str)
3651 assert isinstance(error_location, Location)
3652 assert isinstance(required_subclass, type) or required_subclass is None
3653 assert isinstance(simplified, bool)
3655 simple_name = self.simplified_name(name)
3656 ptr = self
3657 options = []
3659 for ptr in [self] + self.imported:
3660 while ptr:
3661 if simple_name in ptr.table:
3662 rv = ptr.table[simple_name]
3663 if not simplified and rv.name != name: 3663 ↛ 3664line 3663 didn't jump to line 3664 because the condition on line 3663 was never true
3664 mh.error(error_location,
3665 "unknown symbol %s, did you mean %s?" %
3666 (name,
3667 rv.name))
3669 if required_subclass is not None and \
3670 not isinstance(rv, required_subclass):
3671 mh.error(error_location,
3672 "%s %s is not a %s" %
3673 (rv.__class__.__name__,
3674 name,
3675 required_subclass.__name__))
3676 return rv
3677 else:
3678 options += list(item.name
3679 for item in ptr.table.values())
3680 ptr = ptr.parent
3682 matches = get_close_matches(
3683 word = name,
3684 possibilities = options,
3685 n = 1)
3687 if matches:
3688 mh.error(error_location,
3689 "unknown symbol %s, did you mean %s?" %
3690 (name,
3691 matches[0]))
3692 else:
3693 mh.error(error_location,
3694 "unknown symbol %s" % name)
3696 def lookup(self, mh, referencing_token, required_subclass=None):
3697 # lobster-trace: LRM.Described_Name_Equality
3698 assert isinstance(mh, Message_Handler)
3699 assert isinstance(referencing_token, Token)
3700 assert referencing_token.kind in ("IDENTIFIER", "BUILTIN")
3701 assert isinstance(required_subclass, type) or required_subclass is None
3703 return self.lookup_direct(
3704 mh = mh,
3705 name = referencing_token.value,
3706 error_location = referencing_token.location,
3707 required_subclass = required_subclass)
3709 def write_indent(self, indent, message): # pragma: no cover
3710 # lobster-exclude: Debugging feature
3711 assert isinstance(indent, int)
3712 assert indent >= 0
3713 assert isinstance(message, str)
3714 print(" " * (3 * indent) + message)
3716 def dump(self, indent=0, omit_heading=False): # pragma: no cover
3717 # lobster-exclude: Debugging feature
3718 if omit_heading:
3719 new_indent = indent
3720 else:
3721 self.write_indent(indent, "Symbol_Table")
3722 new_indent = indent + 1
3723 ptr = self
3724 while ptr:
3725 for name in ptr.table:
3726 ptr.table[name].dump(new_indent)
3727 ptr = ptr.parent
3729 @classmethod
3730 def create_global_table(cls, mh):
3731 # lobster-trace: LRM.Builtin_Types
3732 # lobster-trace: LRM.Builtin_Functions
3733 # lobster-trace: LRM.Builtin_Type_Conversion_Functions
3734 # lobster-trace: LRM.Signature_Len
3735 # lobster-trace: LRM.Signature_String_End_Functions
3736 # lobster-trace: LRM.Signature_Matches
3738 stab = Symbol_Table()
3739 stab.register(mh, Builtin_Integer())
3740 stab.register(mh, Builtin_Decimal())
3741 stab.register(mh, Builtin_Boolean())
3742 stab.register(mh, Builtin_String())
3743 stab.register(mh, Builtin_Markup_String())
3744 stab.register(mh,
3745 Builtin_Function("len", 1))
3746 stab.register(mh,
3747 Builtin_Function("startswith", 2))
3748 stab.register(mh,
3749 Builtin_Function("endswith", 2))
3750 stab.register(mh,
3751 Builtin_Function("matches", 2))
3752 stab.register(mh,
3753 Builtin_Function("oneof", 1, arity_at_least=True))
3755 return stab
3758class Scope:
3759 def __init__(self):
3760 # lobster-exclude: Constructor only declares variables
3761 self.scope = []
3763 def push(self, stab):
3764 assert isinstance(stab, Symbol_Table)
3765 self.scope.append(stab)
3767 def pop(self):
3768 self.scope.pop()
3770 def contains(self, name):
3771 assert isinstance(name, str)
3773 for stab in reversed(self.scope):
3774 if stab.contains(name):
3775 return True
3776 return False
3778 def lookup(self, mh, referencing_token, required_subclass=None):
3779 assert len(self.scope) >= 1
3780 assert isinstance(mh, Message_Handler)
3781 assert isinstance(referencing_token, Token)
3782 assert referencing_token.kind in ("IDENTIFIER", "BUILTIN")
3783 assert isinstance(required_subclass, type) or required_subclass is None
3785 for stab in reversed(self.scope[1:]):
3786 if stab.contains(referencing_token.value):
3787 return stab.lookup(mh, referencing_token, required_subclass)
3788 return self.scope[0].lookup(mh, referencing_token, required_subclass)
3790 def size(self):
3791 return len(self.scope)