Coverage for trlc/ast.py: 90%
1289 statements
« prev ^ index » next coverage.py v7.10.7, created at 2026-04-14 14:54 +0000
« prev ^ index » next coverage.py v7.10.7, created at 2026-04-14 14:54 +0000
1#!/usr/bin/env python3
2#
3# TRLC - Treat Requirements Like Code
4# Copyright (C) 2022-2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
5# Copyright (C) 2024-2025 Florian Schanda
6#
7# This file is part of the TRLC Python Reference Implementation.
8#
9# TRLC is free software: you can redistribute it and/or modify it
10# under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# TRLC is distributed in the hope that it will be useful, but WITHOUT
15# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
17# License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with TRLC. If not, see <https://www.gnu.org/licenses/>.
22from abc import ABCMeta, abstractmethod
23import re
25from copy import copy
26from difflib import get_close_matches
27from enum import Enum, auto
28from collections import OrderedDict
29from fractions import Fraction
31from trlc.errors import TRLC_Error, Location, Message_Handler
32from trlc.lexer import Token
33from trlc import math
35#
36# This module defines the AST and related object for the TRLC
37# reference implementation. There are four sections:
38#
39# - Valuations deal with concrete values for record objects
40# - AST expressions deal with the syntax tree
41# - AST entities deal with concrete objects that have been declared
42# - Symbol_Table and scope deals with name resolution
43#
46##############################################################################
47# Valuations
48##############################################################################
50class Value:
51 # lobster-trace: LRM.Boolean_Values
52 # lobster-trace: LRM.Integer_Values
53 # lobster-trace: LRM.Decimal_Values
54 # lobster-trace: LRM.String_Values
55 # lobster-trace: LRM.Markup_String_Values
56 """Polymorphic value for evaluating expressions.
58 Any record references will be fully resolved.
60 :attribute location: source location this value comes from
61 :type: Location
63 :attribute value: the value or None (for null values)
64 :type: str, int, bool, fractions.Fraction, list[Value], \
65 Record_Reference, Enumeration_Literal_Spec
67 :attribute typ: type of the value (or None for null values)
68 :type: Type
69 """
70 def __init__(self, location, value, typ):
71 assert isinstance(location, Location)
72 assert value is None or \
73 isinstance(value, (str,
74 int,
75 bool,
76 list, # for arrays
77 dict, # for tuples
78 Fraction,
79 Record_Reference,
80 Enumeration_Literal_Spec))
81 assert typ is None or isinstance(typ, Type)
82 assert (typ is None) == (value is None)
84 self.location = location
85 self.value = value
86 self.typ = typ
88 def __eq__(self, other):
89 return self.typ == other.typ and self.value == other.value
91 def __repr__(self): # pragma: no cover
92 return "Value(%s)" % self.value
94 def resolve_references(self, mh):
95 assert isinstance(mh, Message_Handler)
97 if isinstance(self.value, Record_Reference):
98 self.value.resolve(mh)
101##############################################################################
102# AST Nodes
103##############################################################################
105class Node(metaclass=ABCMeta):
106 """Base class for all AST items.
108 :attribute location: source location
109 :type: Location
110 """
111 def __init__(self, location):
112 # lobster-exclude: Constructor only declares variables
113 assert isinstance(location, Location)
114 self.location = location
116 def set_ast_link(self, tok):
117 assert isinstance(tok, Token)
118 tok.ast_link = self
120 def write_indent(self, indent, message): # pragma: no cover
121 # lobster-exclude: Debugging feature
122 assert isinstance(indent, int)
123 assert indent >= 0
124 assert isinstance(message, str)
125 print(" " * (3 * indent) + message)
127 @abstractmethod
128 def dump(self, indent=0): # pragma: no cover
129 """Visualise the parse tree.
131 This can be called for any :class:`Node` or
132 :class:`Symbol_Table`, and can be very helpful for debugging
133 or understanding the parse tree. The dump methods will produce
134 output like this::
136 Symbol_Table
137 Builtin_Boolean
138 Builtin_Integer
139 Builtin_Decimal
140 Builtin_String
141 Builtin_Markup_String
142 Package bar
143 Symbol_Table
144 Record_Type MyType
145 Composite_Component name
146 Optional: False
147 Type: String
148 Checks
149 Error 'description is too short'
150 Anchor: description
151 Binary Binary_Operator.COMP_GT Expression
152 Type: Boolean
153 Unary Unary_Operator.STRING_LENGTH Expression
154 Type: Integer
155 Name Reference to description
156 Integer Literal 10
157 Package instances
158 Symbol_Table
159 Record_Object SomeThing
160 Type: MyType
161 Field description: "Potato"
162 Builtin_Function endswith
163 Builtin_Function len
164 Builtin_Function matches
165 Builtin_Function startswith
166 Builtin_Function oneof
168 """
169 assert isinstance(indent, int) and indent >= 0
170 assert False, f"dump not implemented for {self.__class__.__name__}"
171 # lobster-exclude: Debugging feature
174class Check_Block(Node):
175 """Node representing check blocks
177 Semantically this has no meaning, but it's nice to have some kind
178 of similar representation to how it's in the file.
180 :attribute n_typ: composite type for which the checks apply
181 :type: Composite_Type
183 :attribute checks: list of checks
184 :type: list[Check]
186 """
187 def __init__(self, location, n_typ):
188 # lobster-trace: LRM.Check_Block
189 super().__init__(location)
190 assert isinstance(n_typ, Composite_Type)
191 self.n_typ = n_typ
192 self.checks = []
194 def add_check(self, n_check):
195 # lobster-trace: LRM.Check_Evaluation_Order
196 assert isinstance(n_check, Check)
197 self.checks.append(n_check)
199 def dump(self, indent=0): # pragma: no cover
200 # lobster-exclude: Debugging feature
201 self.write_indent(indent, "Check_Block")
202 self.write_indent(indent + 1, f"Type: {self.n_typ.name}")
203 for n_check in self.checks:
204 n_check.dump(indent + 1)
207class Compilation_Unit(Node):
208 """Special node to represent the concrete file structure
210 :attribute package: the main package this file declares or contributes to
211 :type: Package
213 :attribute imports: package imported by this file
214 :type: list[Package]
216 :attribute items: list of
217 :type: list[Node]
219 """
220 def __init__(self, file_name):
221 # lobster-exclude: Constructor only declares variables
222 super().__init__(Location(file_name))
223 self.package = None
224 self.imports = None
225 self.raw_imports = []
226 self.items = []
228 def dump(self, indent=0): # pragma: no cover
229 # lobster-exclude: Debugging feature
230 self.write_indent(indent, f"Compilation_Unit ({self.location.file_name})")
231 for t_import in self.raw_imports:
232 self.write_indent(indent + 1, f"Import: {t_import.value}")
233 for n_item in self.items:
234 n_item.dump(indent + 1)
236 def set_package(self, pkg):
237 # lobster-trace: LRM.Current_Package
238 assert isinstance(pkg, Package)
239 self.package = pkg
241 def add_import(self, mh, t_import):
242 # lobster-trace: LRM.Import_Visibility
243 # lobster-trace: LRM.Self_Imports
244 assert isinstance(mh, Message_Handler)
245 assert isinstance(t_import, Token)
246 assert t_import.kind == "IDENTIFIER"
248 if t_import.value == self.package.name:
249 mh.error(t_import.location,
250 "package %s cannot import itself" % self.package.name)
252 # Skip duplicates
253 for t_previous in self.raw_imports:
254 if t_previous.value == t_import.value:
255 mh.warning(t_import.location,
256 "duplicate import of package %s" % t_import.value)
257 return
259 self.raw_imports.append(t_import)
261 def resolve_imports(self, mh, stab):
262 # lobster-trace: LRM.Import_Visibility
263 assert isinstance(mh, Message_Handler)
264 assert isinstance(stab, Symbol_Table)
265 self.imports = set()
266 for t_import in self.raw_imports:
267 # We can ignore errors here, because that just means we
268 # generate more error later.
269 try:
270 a_import = stab.lookup(mh, t_import, Package)
271 self.imports.add(a_import)
272 a_import.set_ast_link(t_import)
273 except TRLC_Error:
274 pass
276 def is_visible(self, n_pkg):
277 # lobster-trace: LRM.Import_Visibility
278 assert self.imports is not None
279 assert isinstance(n_pkg, Package)
280 return n_pkg == self.package or n_pkg in self.imports
282 def add_item(self, node):
283 # lobster-trace: LRM.RSL_File
284 # lobster-trace: LRM.TRLC_File
285 assert isinstance(node, (Concrete_Type,
286 Check_Block,
287 Record_Object)), \
288 "trying to add %s to a compilation unit" % node.__class__.__name__
289 self.items.append(node)
292class Check(Node):
293 """User defined check
295 This represent a single user-defined check inside a check block::
297 checks T {
298 a /= null implies a > 5, warning "potato", a
299 ^^^^^^^^^^^^^^^^^^^^^^^1 ^2 ^3 ^4
301 :attribute n_type: The tuple/record type this check applies to
302 :type: Composite_Type
304 :attribute n_expr: The boolean expression for the check (see 1)
305 :type: Expression
307 :attribute n_anchor: The (optional) record component where the message \
308 should be issued (or None) (see 4)
309 :type: Composite_Component
311 :attribute severity: warning, error, or fatal (see 2; also if this is \
312 not specified the default is 'error')
313 :type: str
315 :attribute message: the user-supplied message (see 3)
316 :type: str
317 """
318 def __init__(self,
319 n_type,
320 n_expr,
321 n_anchor,
322 severity,
323 t_message,
324 extrainfo):
325 # lobster-trace: LRM.Check_Block
326 assert isinstance(n_type, Composite_Type)
327 assert isinstance(n_expr, Expression)
328 assert isinstance(n_anchor, Composite_Component) or n_anchor is None
329 assert severity in ("warning", "error", "fatal")
330 assert isinstance(t_message, Token)
331 assert t_message.kind == "STRING"
332 assert isinstance(extrainfo, str) or extrainfo is None
333 super().__init__(t_message.location)
335 self.n_type = n_type
336 self.n_expr = n_expr
337 self.n_anchor = n_anchor
338 self.severity = severity
339 # lobster-trace: LRM.No_Newlines_In_Message
340 # This is the error recovery strategy if we find newlines in
341 # the short error messages: we just remove them. The error
342 # raised is non-fatal.
343 self.message = t_message.value.replace("\n", " ")
344 self.extrainfo = extrainfo
346 def dump(self, indent=0): # pragma: no cover
347 # lobster-exclude: Debugging feature
348 if self.severity == "warning":
349 self.write_indent(indent, f"Warning '{self.message}'")
350 elif self.severity == "error":
351 self.write_indent(indent, f"Error '{self.message}'")
352 else:
353 self.write_indent(indent, f"Fatal error '{self.message}'")
354 if self.n_anchor:
355 self.write_indent(indent + 1, f"Anchor: {self.n_anchor.name}")
356 self.n_expr.dump(indent + 1)
358 def get_real_location(self, composite_object):
359 # lobster-exclude: LRM.Anchoring
360 assert isinstance(composite_object, (Record_Object,
361 Tuple_Aggregate))
362 if isinstance(composite_object, Record_Object):
363 fields = composite_object.field
364 else:
365 fields = composite_object.value
367 if self.n_anchor is None or fields[self.n_anchor.name] is None:
368 return composite_object.location
369 else:
370 return fields[self.n_anchor.name].location
372 def perform(self, mh, composite_object, gstab):
373 # lobster-trace: LRM.Check_Messages
374 # lobster-trace: LRM.Check_Severity
375 assert isinstance(mh, Message_Handler)
376 assert isinstance(composite_object, (Record_Object,
377 Tuple_Aggregate))
378 assert isinstance(gstab, Symbol_Table)
380 if isinstance(composite_object, Record_Object):
381 result = self.n_expr.evaluate(mh,
382 copy(composite_object.field),
383 gstab)
384 else:
385 result = self.n_expr.evaluate(mh,
386 copy(composite_object.value),
387 gstab)
388 if result.value is None:
389 loc = self.get_real_location(composite_object)
390 mh.error(loc,
391 "check %s (%s) evaluates to null" %
392 (self.n_expr.to_string(),
393 mh.cross_file_reference(self.location)))
395 assert isinstance(result.value, bool)
397 if not result.value:
398 loc = self.get_real_location(composite_object)
399 if self.severity == "warning":
400 mh.warning(location = loc,
401 message = self.message,
402 explanation = self.extrainfo,
403 user = True)
404 else:
405 mh.error(location = loc,
406 message = self.message,
407 explanation = self.extrainfo,
408 fatal = self.severity == "fatal",
409 user = True)
410 return False
412 return True
414##############################################################################
415# AST Nodes (Expressions)
416##############################################################################
419class Unary_Operator(Enum):
420 # lobster-exclude: Utility enumeration for unary operators
421 MINUS = auto()
422 PLUS = auto()
423 LOGICAL_NOT = auto()
424 ABSOLUTE_VALUE = auto()
426 STRING_LENGTH = auto()
427 ARRAY_LENGTH = auto()
429 CONVERSION_TO_INT = auto()
430 CONVERSION_TO_DECIMAL = auto()
433class Binary_Operator(Enum):
434 # lobster-exclude: Utility enumeration for binary operators
435 LOGICAL_AND = auto() # Short-circuit
436 LOGICAL_OR = auto() # Short-circuit
437 LOGICAL_XOR = auto()
438 LOGICAL_IMPLIES = auto() # Short-circuit
440 COMP_EQ = auto()
441 COMP_NEQ = auto()
442 COMP_LT = auto()
443 COMP_LEQ = auto()
444 COMP_GT = auto()
445 COMP_GEQ = auto()
447 STRING_CONTAINS = auto()
448 STRING_STARTSWITH = auto()
449 STRING_ENDSWITH = auto()
450 STRING_REGEX = auto()
452 ARRAY_CONTAINS = auto()
454 PLUS = auto()
455 MINUS = auto()
456 TIMES = auto()
457 DIVIDE = auto()
458 REMAINDER = auto()
460 POWER = auto()
462 INDEX = auto()
465class Expression(Node, metaclass=ABCMeta):
466 """Abstract base class for all expressions.
468 :attribute typ: The type of this expression (or None for null values)
469 :type: Type
470 """
471 def __init__(self, location, typ):
472 # lobster-exclude: Constructor only declares variables
473 super().__init__(location)
474 assert typ is None or isinstance(typ, Type)
475 self.typ = typ
477 def evaluate(self, mh, context, gstab): # pragma: no cover
478 """Evaluate the expression in the given context
480 The context can be None, in which case the expression is
481 evaluated in a static context. Otherwise it must be a
482 dictionary that maps names (such as record fields or
483 quantified variables) to expressions.
485 The global symbol table must be None (for static context
486 evaluations), otherwise it must contain the global symbol
487 table to resolve record references.
489 :param mh: the message handler to use
490 :type mh: Message_Handler
491 :param context: name mapping or None (for a static context)
492 :type context: dict[str, Expression]
493 :raise TRLC_Error: if the expression cannot be evaluated
494 :return: result of the evaluation
495 :rtype: Value
497 """
498 assert isinstance(mh, Message_Handler)
499 assert context is None or isinstance(context, dict)
500 assert gstab is None or isinstance(gstab, Symbol_Table)
501 assert False, "evaluate not implemented for %s" % \
502 self.__class__.__name__
504 @abstractmethod
505 def to_string(self): # pragma: no cover
506 assert False, "to_string not implemented for %s" % \
507 self.__class__.__name__
509 def ensure_type(self, mh, typ):
510 # lobster-trace: LRM.Restricted_Null
511 # lobster-trace: LRM.Null_Is_Invalid
513 assert isinstance(typ, (type, Type))
514 if self.typ is None:
515 mh.error(self.location,
516 "null is not permitted here")
517 elif isinstance(typ, type) and not isinstance(self.typ, typ):
518 mh.error(self.location,
519 "expected expression of type %s, got %s instead" %
520 (typ.__name__,
521 self.typ.__class__.__name__))
522 elif isinstance(typ, Type) and self.typ != typ:
523 mh.error(self.location,
524 "expected expression of type %s, got %s instead" %
525 (typ.name,
526 self.typ.name))
528 def resolve_references(self, mh):
529 assert isinstance(mh, Message_Handler)
531 @abstractmethod
532 def can_be_null(self):
533 """Test if the expression could return null
535 Checks the expression if it could generate a null value
536 *without* raising an error. For example `x` could generate a
537 null value if `x` is a record component that is
538 optional. However `x + 1` could not, since an error would
539 occur earlier.
541 :return: possibility of encountering null
542 :rtype: bool
544 """
545 assert False, "can_be_null not implemented for %s" % \
546 self.__class__.__name__
549class Implicit_Null(Expression):
550 """Synthesised null values
552 When a record object or tuple aggregate is declared and an
553 optional component or field is not specified, we synthesise an
554 implicit null expression for this.
556 For example given this TRLC type::
558 type T {
559 x optional Integer
560 }
562 And this declaration::
564 T Potato {}
566 Then the field mapping for Potato will be::
568 {x: Implicit_Null}
570 Each field will get its own implicit null. Further note that this
571 implicit null is distinct from the explicit :class:`Null_Literal`
572 that can appear in check expressions.
574 """
575 def __init__(self, composite_object, composite_component):
576 # lobster-trace: LRM.Unspecified_Optional_Components
577 assert isinstance(composite_object, (Record_Object,
578 Tuple_Aggregate))
579 assert isinstance(composite_component, Composite_Component)
580 super().__init__(composite_object.location, None)
582 def to_string(self):
583 return "null"
585 def evaluate(self, mh, context, gstab):
586 # lobster-trace: LRM.Unspecified_Optional_Components
587 assert isinstance(mh, Message_Handler)
588 assert context is None or isinstance(context, dict)
589 assert gstab is None or isinstance(gstab, Symbol_Table)
590 return Value(self.location, None, None)
592 def to_python_object(self):
593 return None
595 def dump(self, indent=0): # pragma: no cover
596 # lobster-exclude: Debugging feature
597 self.write_indent(indent, "Implicit_Null")
599 def can_be_null(self):
600 return True
603class Literal(Expression, metaclass=ABCMeta):
604 """Abstract base for all Literals
606 Does not offer any additional features, but it's a nice way to
607 group together all literal types. This is useful if you want to
608 check if you are dealing with a literal::
610 isinstance(my_expression, Literal)
612 """
613 @abstractmethod
614 def to_python_object(self):
615 assert False
618class Null_Literal(Literal):
619 # lobster-trace: LRM.Primary
620 """The null literal
622 This can appear in check expressions::
624 a /= null implies a > 5
625 ^^^^
627 Please note that this is distinct from the :class:`Implicit_Null`
628 values that appear in record objects.
630 """
631 def __init__(self, token):
632 assert isinstance(token, Token)
633 assert token.kind == "KEYWORD"
634 assert token.value == "null"
635 super().__init__(token.location, None)
637 def dump(self, indent=0): # pragma: no cover
638 self.write_indent(indent, "Null Literal")
640 def to_string(self):
641 return "null"
643 def evaluate(self, mh, context, gstab):
644 assert isinstance(mh, Message_Handler)
645 assert context is None or isinstance(context, dict)
646 assert gstab is None or isinstance(gstab, Symbol_Table)
647 return Value(self.location, None, None)
649 def to_python_object(self):
650 return None
652 def can_be_null(self):
653 return True
656class Integer_Literal(Literal):
657 # lobster-trace: LRM.Integer_Values
658 # lobster-trace: LRM.Primary
659 """Integer literals
661 Note that these are always positive. A negative integer is
662 actually a unary negation expression, operating on a positive
663 integer literal::
665 x == -5
667 This would create the following tree::
669 OP_EQUALITY
670 NAME_REFERENCE x
671 UNARY_EXPRESSION -
672 INTEGER_LITERAL 5
674 :attribute value: the non-negative integer value
675 :type: int
676 """
677 def __init__(self, token, typ):
678 assert isinstance(token, Token)
679 assert token.kind == "INTEGER"
680 assert isinstance(typ, Builtin_Integer)
681 super().__init__(token.location, typ)
683 self.value = token.value
685 def dump(self, indent=0): # pragma: no cover
686 self.write_indent(indent, f"Integer Literal {self.value}")
688 def to_string(self):
689 return str(self.value)
691 def evaluate(self, mh, context, gstab):
692 assert isinstance(mh, Message_Handler)
693 assert context is None or isinstance(context, dict)
694 assert gstab is None or isinstance(gstab, Symbol_Table)
695 return Value(self.location, self.value, self.typ)
697 def to_python_object(self):
698 return self.value
700 def can_be_null(self):
701 return False
704class Decimal_Literal(Literal):
705 # lobster-trace: LRM.Decimal_Values
706 # lobster-trace: LRM.Primary
707 """Decimal literals
709 Note that these are always positive. A negative decimal is
710 actually a unary negation expression, operating on a positive
711 decimal literal::
713 x == -5.0
715 This would create the following tree::
717 OP_EQUALITY
718 NAME_REFERENCE x
719 UNARY_EXPRESSION -
720 DECIMAL_LITERAL 5.0
722 :attribute value: the non-negative decimal value
723 :type: fractions.Fraction
724 """
725 def __init__(self, token, typ):
726 assert isinstance(token, Token)
727 assert token.kind == "DECIMAL"
728 assert isinstance(typ, Builtin_Decimal)
729 super().__init__(token.location, typ)
731 self.value = token.value
733 def dump(self, indent=0): # pragma: no cover
734 self.write_indent(indent, f"Decimal Literal {self.value}")
736 def to_string(self):
737 return str(self.value)
739 def evaluate(self, mh, context, gstab):
740 assert isinstance(mh, Message_Handler)
741 assert context is None or isinstance(context, dict)
742 assert gstab is None or isinstance(gstab, Symbol_Table)
743 return Value(self.location, self.value, self.typ)
745 def to_python_object(self):
746 return float(self.value)
748 def can_be_null(self):
749 return False
752class String_Literal(Literal):
753 # lobster-trace: LRM.String_Values
754 # lobster-trace: LRM.Markup_String_Values
755 # lobster-trace: LRM.Primary
756 """String literals
758 Note the value of the string does not include the quotation marks,
759 and any escape sequences are fully resolved. For example::
761 "foo\\"bar"
763 Will have a value of ``foo"bar``.
765 :attribute value: string content
766 :type: str
768 :attribute references: resolved references of a markup string
769 :type: list[Record_Reference]
771 """
772 def __init__(self, token, typ):
773 assert isinstance(token, Token)
774 assert token.kind == "STRING"
775 assert isinstance(typ, Builtin_String)
776 super().__init__(token.location, typ)
778 self.value = token.value
779 self.has_references = isinstance(typ, Builtin_Markup_String)
780 self.references = []
782 def dump(self, indent=0): # pragma: no cover
783 self.write_indent(indent, f"String Literal {repr(self.value)}")
784 if self.has_references:
785 self.write_indent(indent + 1, "Markup References")
786 for ref in self.references:
787 ref.dump(indent + 2)
789 def to_string(self):
790 return self.value
792 def evaluate(self, mh, context, gstab):
793 assert isinstance(mh, Message_Handler)
794 assert context is None or isinstance(context, dict)
795 assert gstab is None or isinstance(gstab, Symbol_Table)
796 return Value(self.location, self.value, self.typ)
798 def to_python_object(self):
799 return self.value
801 def resolve_references(self, mh):
802 assert isinstance(mh, Message_Handler)
803 for ref in self.references:
804 ref.resolve_references(mh)
806 def can_be_null(self):
807 return False
810class Boolean_Literal(Literal):
811 # lobster-trace: LRM.Boolean_Values
812 # lobster-trace: LRM.Primary
813 """Boolean values
815 :attribute value: the boolean value
816 :type: bool
817 """
818 def __init__(self, token, typ):
819 assert isinstance(token, Token)
820 assert token.kind == "KEYWORD"
821 assert token.value in ("false", "true")
822 assert isinstance(typ, Builtin_Boolean)
823 super().__init__(token.location, typ)
825 self.value = token.value == "true"
827 def dump(self, indent=0): # pragma: no cover
828 self.write_indent(indent, f"Boolean Literal {self.value}")
830 def to_string(self):
831 return str(self.value)
833 def evaluate(self, mh, context, gstab):
834 assert isinstance(mh, Message_Handler)
835 assert context is None or isinstance(context, dict)
836 assert gstab is None or isinstance(gstab, Symbol_Table)
837 return Value(self.location, self.value, self.typ)
839 def to_python_object(self):
840 return self.value
842 def can_be_null(self):
843 return False
846class Enumeration_Literal(Literal):
847 """Enumeration values
849 Note that this is distinct from
850 :class:`Enumeration_Literal_Spec`. An enumeration literal is a
851 specific mention of an enumeration member in an expression::
853 foo != my_enum.POTATO
854 ^^^^^^^^^^^^^^
856 To get to the string value of the enumeration literal
857 (i.e. ``POTATO`` here) you can get the name of the literal spec
858 itself: ``enum_lit.value.name``; and to get the name of the
859 enumeration (i.e. ``my_enum`` here) you can use
860 ``enum_lit.value.n_typ.name``.
862 :attribute value: enumeration value
863 :type: Enumeration_Literal_Spec
865 """
866 def __init__(self, location, literal):
867 # lobster-exclude: Constructor only declares variables
868 assert isinstance(literal, Enumeration_Literal_Spec)
869 super().__init__(location, literal.n_typ)
871 self.value = literal
873 def dump(self, indent=0): # pragma: no cover
874 # lobster-exclude: Debugging feature
875 self.write_indent(indent,
876 f"Enumeration Literal {self.typ.name}.{self.value.name}")
878 def to_string(self):
879 return self.typ.name + "." + self.value.name
881 def evaluate(self, mh, context, gstab):
882 assert isinstance(mh, Message_Handler)
883 assert context is None or isinstance(context, dict)
884 assert gstab is None or isinstance(gstab, Symbol_Table)
885 return Value(self.location, self.value, self.typ)
887 def to_python_object(self):
888 return self.value.name
890 def can_be_null(self):
891 return False
894class Array_Aggregate(Expression):
895 """Instances of array types
897 This is created when assigning to array components::
899 potatoes = ["picasso", "yukon gold", "sweet"]
900 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
902 The type of expression that can be found in an array is somewhat
903 limited:
905 * :class:`Literal`
906 * :class:`Array_Aggregate`
907 * :class:`Record_Reference`
909 :attribute value: contents of the array
910 :type: list[Expression]
912 """
913 def __init__(self, location, typ):
914 # lobster-trace: LRM.Record_Object_Declaration
916 super().__init__(location, typ)
917 self.value = []
919 def dump(self, indent=0): # pragma: no cover
920 # lobster-exclude: Debugging feature
921 self.write_indent(indent, "Array_Aggregate")
922 for n_value in self.value:
923 n_value.dump(indent + 1)
925 def append(self, value):
926 assert isinstance(value, (Literal,
927 Unary_Expression,
928 Array_Aggregate,
929 Tuple_Aggregate,
930 Record_Reference))
931 self.value.append(value)
933 def to_string(self):
934 return "[" + ", ".join(x.to_string() for x in self.value) + "]"
936 def evaluate(self, mh, context, gstab):
937 assert isinstance(mh, Message_Handler)
938 assert context is None or isinstance(context, dict)
939 assert gstab is None or isinstance(gstab, Symbol_Table)
940 return Value(self.location,
941 list(element.evaluate(mh, context, gstab)
942 for element in self.value),
943 self.typ)
945 def resolve_references(self, mh):
946 assert isinstance(mh, Message_Handler)
948 for val in self.value:
949 val.resolve_references(mh)
951 def to_python_object(self):
952 return [x.to_python_object() for x in self.value]
954 def can_be_null(self):
955 return False
958class Tuple_Aggregate(Expression):
959 """Instances of a tuple
961 This is created when assigning to a tuple components. There are
962 two forms, the ordinary form::
964 coordinate = (12.3, 40.0)
965 ^^^^^^^^^^^^
967 And the separator form::
969 item = 12345@42
970 ^^^^^^^^
972 In terms of AST there is no difference, as the separator is only
973 syntactic sugar.
975 :attribute value: contents of the tuple
976 :type: dict[str, Expression]
978 """
979 def __init__(self, location, typ):
980 # lobster-trace: LRM.Unspecified_Optional_Components
981 # lobster-trace: LRM.Record_Object_Declaration
983 super().__init__(location, typ)
984 self.value = {n_field.name : Implicit_Null(self, n_field)
985 for n_field in self.typ.components.values()}
987 def assign(self, field, value):
988 assert isinstance(field, str)
989 assert isinstance(value, (Literal,
990 Unary_Expression,
991 Tuple_Aggregate,
992 Record_Reference)), \
993 "value is %s" % value.__class__.__name__
994 assert field in self.typ.components
996 self.value[field] = value
998 def dump(self, indent=0): # pragma: no cover
999 # lobster-exclude: Debugging feature
1000 self.write_indent(indent, "Tuple_Aggregate")
1001 self.write_indent(indent + 1, f"Type: {self.typ.name}")
1002 for n_item in self.typ.iter_sequence():
1003 if isinstance(n_item, Composite_Component):
1004 self.value[n_item.name].dump(indent + 1)
1006 def to_string(self):
1007 first = True
1008 if self.typ.has_separators():
1009 rv = ""
1010 else:
1011 rv = "("
1012 for n_item in self.typ.iter_sequence():
1013 if isinstance(n_item, Separator):
1014 rv += " %s " % n_item.token.value
1015 elif first:
1016 first = False
1017 else:
1018 rv += ", "
1020 if isinstance(n_item, Composite_Component):
1021 rv += self.value[n_item.name].to_string()
1022 if self.typ.has_separators():
1023 rv = ""
1024 else:
1025 rv = ")"
1026 return rv
1028 def evaluate(self, mh, context, gstab):
1029 assert isinstance(mh, Message_Handler)
1030 assert context is None or isinstance(context, dict)
1031 assert gstab is None or isinstance(gstab, Symbol_Table)
1032 return Value(self.location,
1033 {name : element.evaluate(mh, context, gstab)
1034 for name, element in self.value.items()},
1035 self.typ)
1037 def resolve_references(self, mh):
1038 assert isinstance(mh, Message_Handler)
1040 for val in self.value.values():
1041 val.resolve_references(mh)
1043 def to_python_object(self):
1044 return {name: value.to_python_object()
1045 for name, value in self.value.items()}
1047 def can_be_null(self):
1048 return False
1051class Record_Reference(Expression):
1052 """Reference to another record object
1054 This can appear in record object declarations::
1056 Requirement Kitten {
1057 depends_on = Other_Package.Cat
1058 ^1 ^2
1059 }
1061 Note that this is distinct from :class:`Record_Object`. It is just
1062 the name; to get to the object referred to by this you can consult
1063 the target attribute.
1065 The reason we have this indirection is that not all names can be
1066 immediately resolved on parsing in the TRLC language.
1068 Note that while the containing package (see 1) is optional in the
1069 source language, the containing package will always be filled in
1070 in this AST node.
1072 :attribute name: The name of the record (see 2)
1073 :type: str
1075 :attribute target: The concrete record object referred to by (2)
1076 :type: Record_Object
1078 :attribute package: The package (see 1) supposed to contain (2)
1079 :type: Package
1081 """
1082 def __init__(self, location, name, typ, package):
1083 # lobster-exclude: Constructor only declares variables
1084 assert isinstance(location, Location)
1085 assert isinstance(name, str)
1086 assert isinstance(typ, (Record_Type, Union_Type)) or typ is None
1087 assert isinstance(package, Package)
1088 super().__init__(location, typ)
1090 self.name = name
1091 self.target = None
1092 self.package = package
1094 def dump(self, indent=0): # pragma: no cover
1095 # lobster-exclude: Debugging feature
1096 self.write_indent(indent, f"Record Reference {self.name}")
1097 self.write_indent(indent + 1,
1098 f"Resolved: {self.target is not None}")
1100 def to_string(self):
1101 return self.name
1103 def evaluate(self, mh, context, gstab):
1104 assert isinstance(mh, Message_Handler)
1105 assert context is None or isinstance(context, dict)
1106 assert gstab is None or isinstance(gstab, Symbol_Table)
1107 return Value(self.location, copy(self.target.field), self.typ)
1109 def resolve_references(self, mh):
1110 # lobster-trace: LRM.References_To_Extensions
1111 # lobster-trace: LRM.Union_Type_Minimum_Members
1112 assert isinstance(mh, Message_Handler)
1114 self.target = self.package.symbols.lookup_direct(
1115 mh = mh,
1116 name = self.name,
1117 error_location = self.location,
1118 required_subclass = Record_Object)
1119 if self.typ is None:
1120 self.typ = self.target.n_typ
1121 elif isinstance(self.typ, Union_Type):
1122 if not self.typ.is_compatible(self.target.n_typ):
1123 mh.error(self.location,
1124 "expected reference of type %s,"
1125 " but %s is of type %s" %
1126 (self.typ.name,
1127 self.target.name,
1128 self.target.n_typ.name))
1129 elif not self.target.n_typ.is_subclass_of(self.typ):
1130 mh.error(self.location,
1131 "expected reference of type %s, but %s is of type %s" %
1132 (self.typ.name,
1133 self.target.name,
1134 self.target.n_typ.name))
1136 def to_python_object(self):
1137 return self.target.fully_qualified_name()
1139 def can_be_null(self):
1140 return False
1143class Name_Reference(Expression):
1144 # lobster-trace: LRM.Qualified_Name
1145 # lobster-trace: LRM.Static_Regular_Expression
1147 """Reference to a name
1149 Name reference to either a :class:`Composite_Component` or a
1150 :class:`Quantified_Variable`. The actual value of course depends
1151 on the context. See :py:meth:`Expression.evaluate()`.
1153 For example::
1155 (forall x in potato => x > 1)
1156 ^1 ^2
1158 Both indicated parts are a :class:`Name_Reference`, the first one
1159 refers to a :class:`Composite_Component`, and the second refers to a
1160 :class:`Quantified_Variable`.
1162 :attribute entity: the entity named here
1163 :type: Composite_Component, Quantified_Variable
1164 """
1165 def __init__(self, location, entity):
1166 assert isinstance(entity, (Composite_Component,
1167 Quantified_Variable))
1168 super().__init__(location, entity.n_typ)
1169 self.entity = entity
1171 def dump(self, indent=0): # pragma: no cover
1172 self.write_indent(indent, f"Name Reference to {self.entity.name}")
1174 def to_string(self):
1175 return self.entity.name
1177 def evaluate(self, mh, context, gstab):
1178 assert isinstance(mh, Message_Handler)
1179 assert context is None or isinstance(context, dict)
1180 assert gstab is None or isinstance(gstab, Symbol_Table)
1182 if context is None:
1183 mh.error(self.location,
1184 "cannot be used in a static context")
1186 assert self.entity.name in context
1187 return context[self.entity.name].evaluate(mh, context, gstab)
1189 def can_be_null(self):
1190 # The only way we could generate null here (without raising
1191 # error earlier) is when we refer to a component that is
1192 # optional.
1193 if isinstance(self.entity, Composite_Component):
1194 return self.entity.optional
1195 else:
1196 return False
1199class Unary_Expression(Expression):
1200 """Expression with only one operand
1202 This captures the following operations:
1204 * Unary_Operator.PLUS (e.g. ``+5``)
1205 * Unary_Operator.MINUS (e.g. ``-5``)
1206 * Unary_Operator.ABSOLUTE_VALUE (e.g. ``abs 42``)
1207 * Unary_Operator.LOGICAL_NOT (e.g. ``not True``)
1208 * Unary_Operator.STRING_LENGTH (e.g. ``len("foobar")``)
1209 * Unary_Operator.ARRAY_LENGTH (e.g. ``len(component_name)``)
1210 * Unary_Operator.CONVERSION_TO_INT (e.g. ``Integer(5.3)``)
1211 * Unary_Operator.CONVERSION_TO_DECIMAL (e.g. ``Decimal(5)``)
1213 Note that several builtin functions are mapped to unary operators.
1215 :attribute operator: the operation
1216 :type: Unary_Operator
1218 :attribute n_operand: the expression we operate on
1219 :type: Expression
1221 """
1222 def __init__(self, mh, location, typ, operator, n_operand):
1223 # lobster-trace: LRM.Simple_Expression
1224 # lobster-trace: LRM.Relation
1225 # lobster-trace: LRM.Factor
1226 # lobster-trace: LRM.Signature_Len
1227 # lobster-trace: LRM.Signature_Type_Conversion
1229 super().__init__(location, typ)
1230 assert isinstance(mh, Message_Handler)
1231 assert isinstance(operator, Unary_Operator)
1232 assert isinstance(n_operand, Expression)
1233 self.operator = operator
1234 self.n_operand = n_operand
1236 if operator in (Unary_Operator.MINUS,
1237 Unary_Operator.PLUS,
1238 Unary_Operator.ABSOLUTE_VALUE):
1239 self.n_operand.ensure_type(mh, Builtin_Numeric_Type)
1240 elif operator == Unary_Operator.LOGICAL_NOT:
1241 self.n_operand.ensure_type(mh, Builtin_Boolean)
1242 elif operator == Unary_Operator.STRING_LENGTH:
1243 self.n_operand.ensure_type(mh, Builtin_String)
1244 elif operator == Unary_Operator.ARRAY_LENGTH:
1245 self.n_operand.ensure_type(mh, Array_Type)
1246 elif operator == Unary_Operator.CONVERSION_TO_INT:
1247 self.n_operand.ensure_type(mh, Builtin_Numeric_Type)
1248 elif operator == Unary_Operator.CONVERSION_TO_DECIMAL:
1249 self.n_operand.ensure_type(mh, Builtin_Numeric_Type)
1250 else:
1251 mh.ice_loc(self.location,
1252 "unexpected unary operation %s" % operator)
1254 def to_string(self):
1255 prefix_operators = {
1256 Unary_Operator.MINUS : "-",
1257 Unary_Operator.PLUS : "+",
1258 Unary_Operator.ABSOLUTE_VALUE : "abs ",
1259 Unary_Operator.LOGICAL_NOT : "not ",
1260 }
1261 function_calls = {
1262 Unary_Operator.STRING_LENGTH : "len",
1263 Unary_Operator.ARRAY_LENGTH : "len",
1264 Unary_Operator.CONVERSION_TO_INT : "Integer",
1265 Unary_Operator.CONVERSION_TO_DECIMAL : "Decimal"
1266 }
1268 if self.operator in prefix_operators:
1269 return prefix_operators[self.operator] + \
1270 self.n_operand.to_string()
1272 elif self.operator in function_calls:
1273 return "%s(%s)" % (function_calls[self.operator],
1274 self.n_operand.to_string())
1276 else:
1277 assert False
1279 def dump(self, indent=0): # pragma: no cover
1280 # lobster-exclude: Debugging feature
1281 self.write_indent(indent, f"Unary {self.operator} Expression")
1282 self.write_indent(indent + 1, f"Type: {self.typ.name}")
1283 self.n_operand.dump(indent + 1)
1285 def evaluate(self, mh, context, gstab):
1286 # lobster-trace: LRM.Null_Is_Invalid
1287 # lobster-trace: LRM.Signature_Len
1288 # lobster-trace: LRM.Signature_Type_Conversion
1289 # lobster-trace: LRM.Len_Semantics
1290 # lobster-trace: LRM.Integer_Conversion_Semantics
1291 # lobster-trace: LRM.Decimal_Conversion_Semantics
1293 assert isinstance(mh, Message_Handler)
1294 assert context is None or isinstance(context, dict)
1295 assert gstab is None or isinstance(gstab, Symbol_Table)
1297 v_operand = self.n_operand.evaluate(mh, context, gstab)
1298 if v_operand.value is None: 1298 ↛ 1299line 1298 didn't jump to line 1299 because the condition on line 1298 was never true
1299 mh.error(v_operand.location,
1300 "input to unary expression %s (%s) must not be null" %
1301 (self.to_string(),
1302 mh.cross_file_reference(self.location)))
1304 if self.operator == Unary_Operator.MINUS:
1305 return Value(location = self.location,
1306 value = -v_operand.value,
1307 typ = self.typ)
1308 elif self.operator == Unary_Operator.PLUS:
1309 return Value(location = self.location,
1310 value = +v_operand.value,
1311 typ = self.typ)
1312 elif self.operator == Unary_Operator.LOGICAL_NOT:
1313 return Value(location = self.location,
1314 value = not v_operand.value,
1315 typ = self.typ)
1316 elif self.operator == Unary_Operator.ABSOLUTE_VALUE:
1317 return Value(location = self.location,
1318 value = abs(v_operand.value),
1319 typ = self.typ)
1320 elif self.operator in (Unary_Operator.STRING_LENGTH,
1321 Unary_Operator.ARRAY_LENGTH):
1322 return Value(location = self.location,
1323 value = len(v_operand.value),
1324 typ = self.typ)
1325 elif self.operator == Unary_Operator.CONVERSION_TO_INT:
1326 if isinstance(v_operand.value, Fraction): 1326 ↛ 1332line 1326 didn't jump to line 1332 because the condition on line 1326 was always true
1327 return Value(
1328 location = self.location,
1329 value = math.round_nearest_away(v_operand.value),
1330 typ = self.typ)
1331 else:
1332 return Value(location = self.location,
1333 value = v_operand.value,
1334 typ = self.typ)
1335 elif self.operator == Unary_Operator.CONVERSION_TO_DECIMAL:
1336 return Value(location = self.location,
1337 value = Fraction(v_operand.value),
1338 typ = self.typ)
1339 else:
1340 mh.ice_loc(self.location,
1341 "unexpected unary operation %s" % self.operator)
1343 def to_python_object(self):
1344 assert self.operator in (Unary_Operator.MINUS,
1345 Unary_Operator.PLUS)
1346 val = self.n_operand.to_python_object()
1347 if self.operator == Unary_Operator.MINUS:
1348 return -val
1349 else:
1350 return val
1352 def can_be_null(self):
1353 return False
1356class Binary_Expression(Expression):
1357 """Expression with two operands
1359 This captures the following operations:
1361 * Binary_Operator.LOGICAL_AND (e.g. ``a and b``)
1362 * Binary_Operator.LOGICAL_OR (e.g. ``a or b``)
1363 * Binary_Operator.LOGICAL_XOR (e.g. ``a xor b``)
1364 * Binary_Operator.LOGICAL_IMPLIES (e.g. ``a implies b``)
1365 * Binary_Operator.COMP_EQ (e.g. ``a == null``)
1366 * Binary_Operator.COMP_NEQ (e.g. ``a != null``)
1367 * Binary_Operator.COMP_LT (e.g. ``1 < 2``)
1368 * Binary_Operator.COMP_LEQ (e.g. ``1 <= 2``)
1369 * Binary_Operator.COMP_GT (e.g. ``a > b``)
1370 * Binary_Operator.COMP_GEQ (e.g. ``a >= b``)
1371 * Binary_Operator.STRING_CONTAINS (e.g. ``"foo" in "foobar"``)
1372 * Binary_Operator.STRING_STARTSWITH (e.g. ``startswith("foo", "f")``)
1373 * Binary_Operator.STRING_ENDSWITH (e.g. ``endswith("foo", "o")``)
1374 * Binary_Operator.STRING_REGEX (e.g. ``matches("foo", ".o.``)
1375 * Binary_Operator.ARRAY_CONTAINS (e.g. ``42 in arr``)
1376 * Binary_Operator.PLUS (e.g. ``42 + b`` or ``"foo" + bar``)
1377 * Binary_Operator.MINUS (e.g. ``a - 1``)
1378 * Binary_Operator.TIMES (e.g. ``2 * x``)
1379 * Binary_Operator.DIVIDE (e.g. ``x / 2``)
1380 * Binary_Operator.REMAINDER (e.g. ``x % 2``)
1381 * Binary_Operator.POWER (e.g. ``x ** 2``)
1382 * Binary_Operator.INDEX (e.g. ``foo[2]``)
1384 Note that several builtin functions are mapped to unary operators.
1386 Note also that the plus operation is supported for integers,
1387 rationals and strings.
1389 :attribute operator: the operation
1390 :type: Binary_Operator
1392 :attribute n_lhs: the first operand
1393 :type: Expression
1395 :attribute n_rhs: the second operand
1396 :type: Expression
1398 """
1399 def __init__(self, mh, location, typ, operator, n_lhs, n_rhs):
1400 # lobster-trace: LRM.Expression
1401 # lobster-trace: LRM.Relation
1402 # lobster-trace: LRM.Simple_Expression
1403 # lobster-trace: LRM.Term
1404 # lobster-trace: LRM.Factor
1405 # lobster-trace: LRM.Signature_String_End_Functions
1406 # lobster-trace: LRM.Signature_Matches
1408 super().__init__(location, typ)
1409 assert isinstance(mh, Message_Handler)
1410 assert isinstance(operator, Binary_Operator)
1411 assert isinstance(n_lhs, Expression)
1412 assert isinstance(n_rhs, Expression)
1413 self.operator = operator
1414 self.n_lhs = n_lhs
1415 self.n_rhs = n_rhs
1417 if operator in (Binary_Operator.LOGICAL_AND,
1418 Binary_Operator.LOGICAL_OR,
1419 Binary_Operator.LOGICAL_XOR,
1420 Binary_Operator.LOGICAL_IMPLIES):
1421 self.n_lhs.ensure_type(mh, Builtin_Boolean)
1422 self.n_rhs.ensure_type(mh, Builtin_Boolean)
1424 elif operator in (Binary_Operator.COMP_EQ,
1425 Binary_Operator.COMP_NEQ):
1426 # lobster-trace: LRM.Union_Type_Equality
1427 # lobster-trace: LRM.Union_Type_Equality_Domain
1428 if (self.n_lhs.typ is None) or (self.n_rhs.typ is None):
1429 # We can compary anything to null (including itself)
1430 pass
1431 elif isinstance(self.n_lhs.typ, Union_Type) or \
1432 isinstance(self.n_rhs.typ, Union_Type):
1433 # For union types, we allow comparison if both
1434 # sides are record-like (Record_Type or Union_Type)
1435 lhs_is_record = isinstance(self.n_lhs.typ,
1436 (Record_Type, Union_Type))
1437 rhs_is_record = isinstance(self.n_rhs.typ,
1438 (Record_Type, Union_Type))
1439 if not (lhs_is_record and rhs_is_record): 1439 ↛ 1440line 1439 didn't jump to line 1440 because the condition on line 1439 was never true
1440 mh.error(self.location,
1441 "type mismatch: %s and %s do not match" %
1442 (self.n_lhs.typ.name,
1443 self.n_rhs.typ.name))
1444 else:
1445 # Check that there is at least one pair of member
1446 # types (one from each side) where one is a subtype
1447 # of the other. This implements Equality_Domain for
1448 # union types: an unrelated record type is rejected.
1449 lhs_members = (self.n_lhs.typ.types
1450 if isinstance(self.n_lhs.typ, Union_Type)
1451 else [self.n_lhs.typ])
1452 rhs_members = (self.n_rhs.typ.types
1453 if isinstance(self.n_rhs.typ, Union_Type)
1454 else [self.n_rhs.typ])
1455 if not any(lm.is_subclass_of(rm) or rm.is_subclass_of(lm)
1456 for lm in lhs_members
1457 for rm in rhs_members):
1458 mh.error(self.location,
1459 "type mismatch: %s and %s do not match" %
1460 (self.n_lhs.typ.name,
1461 self.n_rhs.typ.name))
1462 elif self.n_lhs.typ != self.n_rhs.typ:
1463 # Otherwise we can compare anything, as long as the
1464 # types match
1465 mh.error(self.location,
1466 "type mismatch: %s and %s do not match" %
1467 (self.n_lhs.typ.name,
1468 self.n_rhs.typ.name))
1470 elif operator in (Binary_Operator.COMP_LT,
1471 Binary_Operator.COMP_LEQ,
1472 Binary_Operator.COMP_GT,
1473 Binary_Operator.COMP_GEQ):
1474 self.n_lhs.ensure_type(mh, Builtin_Numeric_Type)
1475 self.n_rhs.ensure_type(mh, self.n_lhs.typ)
1477 elif operator in (Binary_Operator.STRING_CONTAINS,
1478 Binary_Operator.STRING_STARTSWITH,
1479 Binary_Operator.STRING_ENDSWITH,
1480 Binary_Operator.STRING_REGEX):
1481 self.n_lhs.ensure_type(mh, Builtin_String)
1482 self.n_rhs.ensure_type(mh, Builtin_String)
1484 elif operator == Binary_Operator.ARRAY_CONTAINS:
1485 self.n_rhs.ensure_type(mh, Array_Type)
1486 self.n_lhs.ensure_type(mh, self.n_rhs.typ.element_type.__class__)
1488 elif operator == Binary_Operator.PLUS:
1489 if isinstance(self.n_lhs.typ, Builtin_Numeric_Type):
1490 self.n_rhs.ensure_type(mh, self.n_lhs.typ)
1491 else:
1492 self.n_lhs.ensure_type(mh, Builtin_String)
1493 self.n_rhs.ensure_type(mh, Builtin_String)
1495 elif operator in (Binary_Operator.MINUS,
1496 Binary_Operator.TIMES,
1497 Binary_Operator.DIVIDE):
1498 self.n_lhs.ensure_type(mh, Builtin_Numeric_Type)
1499 self.n_rhs.ensure_type(mh, self.n_lhs.typ)
1501 elif operator == Binary_Operator.POWER:
1502 self.n_lhs.ensure_type(mh, Builtin_Numeric_Type)
1503 self.n_rhs.ensure_type(mh, Builtin_Integer)
1505 elif operator == Binary_Operator.REMAINDER:
1506 self.n_lhs.ensure_type(mh, Builtin_Integer)
1507 self.n_rhs.ensure_type(mh, Builtin_Integer)
1509 elif operator == Binary_Operator.INDEX:
1510 self.n_lhs.ensure_type(mh, Array_Type)
1511 self.n_rhs.ensure_type(mh, Builtin_Integer)
1513 else:
1514 mh.ice_loc(self.location,
1515 "unexpected binary operation %s" % operator)
1517 def dump(self, indent=0): # pragma: no cover
1518 # lobster-exclude: Debugging feature
1519 self.write_indent(indent, f"Binary {self.operator} Expression")
1520 self.write_indent(indent + 1, f"Type: {self.typ.name}")
1521 self.n_lhs.dump(indent + 1)
1522 self.n_rhs.dump(indent + 1)
1524 def to_string(self):
1525 infix_operators = {
1526 Binary_Operator.LOGICAL_AND : "and",
1527 Binary_Operator.LOGICAL_OR : "or",
1528 Binary_Operator.LOGICAL_XOR : "xor",
1529 Binary_Operator.LOGICAL_IMPLIES : "implies",
1530 Binary_Operator.COMP_EQ : "==",
1531 Binary_Operator.COMP_NEQ : "!=",
1532 Binary_Operator.COMP_LT : "<",
1533 Binary_Operator.COMP_LEQ : "<=",
1534 Binary_Operator.COMP_GT : ">",
1535 Binary_Operator.COMP_GEQ : ">=",
1536 Binary_Operator.STRING_CONTAINS : "in",
1537 Binary_Operator.ARRAY_CONTAINS : "in",
1538 Binary_Operator.PLUS : "+",
1539 Binary_Operator.MINUS : "-",
1540 Binary_Operator.TIMES : "*",
1541 Binary_Operator.DIVIDE : "/",
1542 Binary_Operator.REMAINDER : "%",
1543 Binary_Operator.POWER : "**",
1544 }
1545 string_functions = {
1546 Binary_Operator.STRING_STARTSWITH : "startswith",
1547 Binary_Operator.STRING_ENDSWITH : "endswith",
1548 Binary_Operator.STRING_REGEX : "matches",
1549 }
1551 if self.operator in infix_operators:
1552 return "%s %s %s" % (self.n_lhs.to_string(),
1553 infix_operators[self.operator],
1554 self.n_rhs.to_string())
1556 elif self.operator in string_functions:
1557 return "%s(%s, %s)" % (string_functions[self.operator],
1558 self.n_lhs.to_string(),
1559 self.n_rhs.to_string())
1561 elif self.operator == Binary_Operator.INDEX:
1562 return "%s[%s]" % (self.n_lhs.to_string(),
1563 self.n_rhs.to_string())
1565 else:
1566 assert False
1568 def evaluate(self, mh, context, gstab):
1569 # lobster-trace: LRM.Null_Equivalence
1570 # lobster-trace: LRM.Null_Is_Invalid
1571 # lobster-trace: LRM.Signature_String_End_Functions
1572 # lobster-trace: LRM.Signature_Matches
1573 # lobster-trace: LRM.Startswith_Semantics
1574 # lobster-trace: LRM.Endswith_Semantics
1575 # lobster-trace: LRM.Matches_Semantics
1577 assert isinstance(mh, Message_Handler)
1578 assert context is None or isinstance(context, dict)
1579 assert gstab is None or isinstance(gstab, Symbol_Table)
1581 v_lhs = self.n_lhs.evaluate(mh, context, gstab)
1582 if v_lhs.value is None and \
1583 self.operator not in (Binary_Operator.COMP_EQ,
1584 Binary_Operator.COMP_NEQ):
1585 mh.error(v_lhs.location,
1586 "lhs of check %s (%s) must not be null" %
1587 (self.to_string(),
1588 mh.cross_file_reference(self.location)))
1590 # Check for the short-circuit operators first
1591 if self.operator == Binary_Operator.LOGICAL_AND:
1592 assert isinstance(v_lhs.value, bool)
1593 if v_lhs.value:
1594 return self.n_rhs.evaluate(mh, context, gstab)
1595 else:
1596 return v_lhs
1598 elif self.operator == Binary_Operator.LOGICAL_OR:
1599 assert isinstance(v_lhs.value, bool)
1600 if v_lhs.value:
1601 return v_lhs
1602 else:
1603 return self.n_rhs.evaluate(mh, context, gstab)
1605 elif self.operator == Binary_Operator.LOGICAL_IMPLIES:
1606 assert isinstance(v_lhs.value, bool)
1607 if v_lhs.value:
1608 return self.n_rhs.evaluate(mh, context, gstab)
1609 else:
1610 return Value(location = self.location,
1611 value = True,
1612 typ = self.typ)
1614 # Otherwise, evaluate RHS and do the operation
1615 v_rhs = self.n_rhs.evaluate(mh, context, gstab)
1616 if v_rhs.value is None and \
1617 self.operator not in (Binary_Operator.COMP_EQ,
1618 Binary_Operator.COMP_NEQ):
1619 mh.error(v_rhs.location,
1620 "rhs of check %s (%s) must not be null" %
1621 (self.to_string(),
1622 mh.cross_file_reference(self.location)))
1624 if self.operator == Binary_Operator.LOGICAL_XOR:
1625 assert isinstance(v_lhs.value, bool)
1626 assert isinstance(v_rhs.value, bool)
1627 return Value(location = self.location,
1628 value = v_lhs.value ^ v_rhs.value,
1629 typ = self.typ)
1631 elif self.operator == Binary_Operator.COMP_EQ:
1632 return Value(location = self.location,
1633 value = v_lhs.value == v_rhs.value,
1634 typ = self.typ)
1636 elif self.operator == Binary_Operator.COMP_NEQ:
1637 return Value(location = self.location,
1638 value = v_lhs.value != v_rhs.value,
1639 typ = self.typ)
1641 elif self.operator in (Binary_Operator.COMP_LT,
1642 Binary_Operator.COMP_LEQ,
1643 Binary_Operator.COMP_GT,
1644 Binary_Operator.COMP_GEQ):
1645 return Value(
1646 location = self.location,
1647 value = {
1648 Binary_Operator.COMP_LT : lambda lhs, rhs: lhs < rhs,
1649 Binary_Operator.COMP_LEQ : lambda lhs, rhs: lhs <= rhs,
1650 Binary_Operator.COMP_GT : lambda lhs, rhs: lhs > rhs,
1651 Binary_Operator.COMP_GEQ : lambda lhs, rhs: lhs >= rhs,
1652 }[self.operator](v_lhs.value, v_rhs.value),
1653 typ = self.typ)
1655 elif self.operator == Binary_Operator.STRING_CONTAINS:
1656 assert isinstance(v_lhs.value, str)
1657 assert isinstance(v_rhs.value, str)
1659 return Value(location = self.location,
1660 value = v_lhs.value in v_rhs.value,
1661 typ = self.typ)
1663 elif self.operator == Binary_Operator.STRING_STARTSWITH:
1664 assert isinstance(v_lhs.value, str)
1665 assert isinstance(v_rhs.value, str)
1666 return Value(location = self.location,
1667 value = v_lhs.value.startswith(v_rhs.value),
1668 typ = self.typ)
1670 elif self.operator == Binary_Operator.STRING_ENDSWITH:
1671 assert isinstance(v_lhs.value, str)
1672 assert isinstance(v_rhs.value, str)
1673 return Value(location = self.location,
1674 value = v_lhs.value.endswith(v_rhs.value),
1675 typ = self.typ)
1677 elif self.operator == Binary_Operator.STRING_REGEX:
1678 assert isinstance(v_lhs.value, str)
1679 assert isinstance(v_rhs.value, str)
1680 return Value(location = self.location,
1681 value = re.match(v_rhs.value,
1682 v_lhs.value) is not None,
1683 typ = self.typ)
1685 elif self.operator == Binary_Operator.ARRAY_CONTAINS:
1686 assert isinstance(v_rhs.value, list)
1688 return Value(location = self.location,
1689 value = v_lhs in v_rhs.value,
1690 typ = self.typ)
1692 elif self.operator == Binary_Operator.PLUS:
1693 assert isinstance(v_lhs.value, (int, str, Fraction))
1694 assert isinstance(v_rhs.value, (int, str, Fraction))
1695 return Value(location = self.location,
1696 value = v_lhs.value + v_rhs.value,
1697 typ = self.typ)
1699 elif self.operator == Binary_Operator.MINUS:
1700 assert isinstance(v_lhs.value, (int, Fraction))
1701 assert isinstance(v_rhs.value, (int, Fraction))
1702 return Value(location = self.location,
1703 value = v_lhs.value - v_rhs.value,
1704 typ = self.typ)
1706 elif self.operator == Binary_Operator.TIMES:
1707 assert isinstance(v_lhs.value, (int, Fraction))
1708 assert isinstance(v_rhs.value, (int, Fraction))
1709 return Value(location = self.location,
1710 value = v_lhs.value * v_rhs.value,
1711 typ = self.typ)
1713 elif self.operator == Binary_Operator.DIVIDE:
1714 assert isinstance(v_lhs.value, (int, Fraction))
1715 assert isinstance(v_rhs.value, (int, Fraction))
1717 if v_rhs.value == 0: 1717 ↛ 1718line 1717 didn't jump to line 1718 because the condition on line 1717 was never true
1718 mh.error(v_rhs.location,
1719 "division by zero in %s (%s)" %
1720 (self.to_string(),
1721 mh.cross_file_reference(self.location)))
1723 if isinstance(v_lhs.value, int):
1724 return Value(location = self.location,
1725 value = v_lhs.value // v_rhs.value,
1726 typ = self.typ)
1727 else:
1728 return Value(location = self.location,
1729 value = v_lhs.value / v_rhs.value,
1730 typ = self.typ)
1732 elif self.operator == Binary_Operator.REMAINDER:
1733 assert isinstance(v_lhs.value, int)
1734 assert isinstance(v_rhs.value, int)
1736 if v_rhs.value == 0: 1736 ↛ 1737line 1736 didn't jump to line 1737 because the condition on line 1736 was never true
1737 mh.error(v_rhs.location,
1738 "division by zero in %s (%s)" %
1739 (self.to_string(),
1740 mh.cross_file_reference(self.location)))
1742 return Value(location = self.location,
1743 value = math.remainder(v_lhs.value, v_rhs.value),
1744 typ = self.typ)
1746 elif self.operator == Binary_Operator.POWER:
1747 assert isinstance(v_lhs.value, (int, Fraction))
1748 assert isinstance(v_rhs.value, int)
1749 return Value(location = self.location,
1750 value = v_lhs.value ** v_rhs.value,
1751 typ = self.typ)
1753 elif self.operator == Binary_Operator.INDEX:
1754 assert isinstance(v_lhs.value, list)
1755 assert isinstance(v_rhs.value, int)
1757 if v_rhs.value < 0: 1757 ↛ 1758line 1757 didn't jump to line 1758 because the condition on line 1757 was never true
1758 mh.error(v_rhs.location,
1759 "index cannot be less than zero in %s (%s)" %
1760 (self.to_string(),
1761 mh.cross_file_reference(self.location)))
1762 elif v_lhs.typ.upper_bound is not None and \ 1762 ↛ 1764line 1762 didn't jump to line 1764 because the condition on line 1762 was never true
1763 v_rhs.value > v_lhs.typ.upper_bound:
1764 mh.error(v_rhs.location,
1765 "index cannot be more than %u in %s (%s)" %
1766 (v_lhs.typ.upper_bound,
1767 self.to_string(),
1768 mh.cross_file_reference(self.location)))
1769 elif v_rhs.value > len(v_lhs.value): 1769 ↛ 1770line 1769 didn't jump to line 1770 because the condition on line 1769 was never true
1770 mh.error(v_lhs.location,
1771 "array is not big enough in %s (%s)" %
1772 (self.to_string(),
1773 mh.cross_file_reference(self.location)))
1775 return Value(location = self.location,
1776 value = v_lhs.value[v_rhs.value].value,
1777 typ = self.typ)
1779 else:
1780 mh.ice_loc(self.location,
1781 "unexpected binary operator %s" % self.operator)
1783 def can_be_null(self):
1784 return False
1787class Field_Access_Expression(Expression):
1788 """Tuple, Record, or Union field access
1790 For example in::
1792 foo.bar
1793 ^1 ^2
1795 :attribute n_prefix: expression with tuple, record, or union type (see 1)
1796 :type: Expression
1798 :attribute n_field: a field to dereference (see 2)
1799 :type: Composite_Component
1801 :attribute is_union_access: True if the prefix is a union type
1802 :type: bool
1804 :attribute is_universal: True if field exists in all union members.
1805 Only meaningful when is_union_access is True.
1806 :type: bool
1808 """
1809 def __init__(self, mh, location, n_prefix, n_field,
1810 is_union_access=False, is_universal=True):
1811 # lobster-trace: LRM.Union_Type_Field_Access
1812 assert isinstance(mh, Message_Handler)
1813 assert isinstance(n_prefix, Expression)
1814 assert isinstance(n_field, Composite_Component)
1815 assert isinstance(is_union_access, bool)
1816 assert isinstance(is_universal, bool)
1817 super().__init__(location, n_field.n_typ)
1818 self.n_prefix = n_prefix
1819 self.n_field = n_field
1820 self.is_union_access = is_union_access
1821 self.is_universal = is_universal
1823 if not is_union_access:
1824 self.n_prefix.ensure_type(mh, self.n_field.member_of)
1826 def dump(self, indent=0): # pragma: no cover
1827 # lobster-exclude: Debugging feature
1828 self.write_indent(indent, f"Field_Access ({self.n_field.name})")
1829 self.n_prefix.dump(indent + 1)
1831 def to_string(self):
1832 return self.n_prefix.to_string() + "." + self.n_field.name
1834 def evaluate(self, mh, context, gstab):
1835 assert isinstance(mh, Message_Handler)
1836 assert context is None or isinstance(context, dict)
1837 assert gstab is None or isinstance(gstab, Symbol_Table)
1839 v_prefix = self.n_prefix.evaluate(mh,
1840 context,
1841 gstab).value
1842 if v_prefix is None:
1843 # lobster-trace: LRM.Dereference
1844 mh.error(self.n_prefix.location,
1845 "null dereference")
1847 # lobster-trace: LRM.Union_Type_Partial_Field_Access
1848 # lobster-trace: LRM.Union_Type_Partial_Field_Null
1849 if self.n_field.name not in v_prefix:
1850 return Value(self.location, None, None)
1852 v_field = v_prefix[self.n_field.name]
1853 if isinstance(v_field, Implicit_Null):
1854 return v_field.evaluate(mh, context, gstab)
1855 else:
1856 return v_field
1858 def can_be_null(self):
1859 # A union field access on a partial field (not present in all
1860 # member types) evaluates to null at runtime, so we must
1861 # report True in that case.
1862 return self.is_union_access and not self.is_universal
1865class Range_Test(Expression):
1866 """Range membership test
1868 For example in::
1870 x in 1 .. field+1
1871 ^lhs ^lower ^^^^^^^upper
1873 Note that none of these are guaranteed to be literals or names;
1874 you can have arbitrarily complex expressions here.
1876 :attribute n_lhs: the expression to test
1877 :type: Expression
1879 :attribute n_lower: the lower bound
1880 :type: Expression
1882 :attribute n_upper: the upper bound
1883 :type: Expression
1885 """
1886 def __init__(self, mh, location, typ, n_lhs, n_lower, n_upper):
1887 # lobster-trace: LRM.Relation
1888 super().__init__(location, typ)
1889 assert isinstance(mh, Message_Handler)
1890 assert isinstance(n_lhs, Expression)
1891 assert isinstance(n_lower, Expression)
1892 assert isinstance(n_upper, Expression)
1893 self.n_lhs = n_lhs
1894 self.n_lower = n_lower
1895 self.n_upper = n_upper
1897 self.n_lhs.ensure_type(mh, Builtin_Numeric_Type)
1898 self.n_lower.ensure_type(mh, self.n_lhs.typ)
1899 self.n_upper.ensure_type(mh, self.n_lhs.typ)
1901 def to_string(self):
1902 return "%s in %s .. %s" % (self.n_lhs.to_string(),
1903 self.n_lower.to_string(),
1904 self.n_upper.to_string())
1906 def dump(self, indent=0): # pragma: no cover
1907 # lobster-exclude: Debugging feature
1908 self.write_indent(indent, "Range Test")
1909 self.write_indent(indent + 1, f"Type: {self.typ}")
1910 self.n_lhs.dump(indent + 1)
1911 self.n_lower.dump(indent + 1)
1912 self.n_upper.dump(indent + 1)
1914 def evaluate(self, mh, context, gstab):
1915 # lobster-trace: LRM.Null_Is_Invalid
1916 assert isinstance(mh, Message_Handler)
1917 assert context is None or isinstance(context, dict)
1918 assert gstab is None or isinstance(gstab, Symbol_Table)
1920 v_lhs = self.n_lhs.evaluate(mh, context, gstab)
1921 if v_lhs.value is None: 1921 ↛ 1922line 1921 didn't jump to line 1922 because the condition on line 1921 was never true
1922 mh.error(v_lhs.location,
1923 "lhs of range check %s (%s) see must not be null" %
1924 (self.to_string(),
1925 mh.cross_file_reference(self.location)))
1927 v_lower = self.n_lower.evaluate(mh, context, gstab)
1928 if v_lower.value is None: 1928 ↛ 1929line 1928 didn't jump to line 1929 because the condition on line 1928 was never true
1929 mh.error(v_lower.location,
1930 "lower bound of range check %s (%s) must not be null" %
1931 (self.to_string(),
1932 mh.cross_file_reference(self.location)))
1934 v_upper = self.n_upper.evaluate(mh, context, gstab)
1935 if v_upper.value is None: 1935 ↛ 1936line 1935 didn't jump to line 1936 because the condition on line 1935 was never true
1936 mh.error(v_upper.location,
1937 "upper bound of range check %s (%s) must not be null" %
1938 (self.to_string(),
1939 mh.cross_file_reference(self.location)))
1941 return Value(location = self.location,
1942 value = v_lower.value <= v_lhs.value <= v_upper.value,
1943 typ = self.typ)
1945 def can_be_null(self):
1946 return False
1949class OneOf_Expression(Expression):
1950 """OneOf expression
1952 For example in::
1954 oneof(a, b, c)
1955 ^^^^^^^ choices
1957 :attribute choices: a list of boolean expressions to test
1958 :type: list[Expression]
1959 """
1960 def __init__(self, mh, location, typ, choices):
1961 # lobster-trace: LRM.Signature_OneOf
1962 super().__init__(location, typ)
1963 assert isinstance(typ, Builtin_Boolean)
1964 assert isinstance(mh, Message_Handler)
1965 assert isinstance(choices, list)
1966 assert all(isinstance(item, Expression)
1967 for item in choices)
1968 self.choices = choices
1970 for n_choice in choices:
1971 n_choice.ensure_type(mh, Builtin_Boolean)
1973 def to_string(self):
1974 return "oneof(%s)" % ", ".join(n_choice.to_string()
1975 for n_choice in self.choices)
1977 def dump(self, indent=0): # pragma: no cover
1978 # lobster-exclude: Debugging feature
1979 self.write_indent(indent, "OneOf Test")
1980 self.write_indent(indent + 1, f"Type: {self.typ}")
1981 for n_choice in self.choices:
1982 n_choice.dump(indent + 1)
1984 def evaluate(self, mh, context, gstab):
1985 # lobster-trace: LRM.OneOf_Semantics
1986 assert isinstance(mh, Message_Handler)
1987 assert context is None or isinstance(context, dict)
1988 assert gstab is None or isinstance(gstab, Symbol_Table)
1990 v_choices = [n_choice.evaluate(mh, context, gstab).value
1991 for n_choice in self.choices]
1993 return Value(location = self.location,
1994 value = v_choices.count(True) == 1,
1995 typ = self.typ)
1997 def can_be_null(self):
1998 return False
2001class Action(Node):
2002 """An if or elseif part inside a conditional expression
2004 Each :class:`Conditional_Expression` is made up of a sequence of
2005 Actions. For example here is a single expression with two
2006 Actions::
2008 (if x == 0 then "zero" elsif x == 1 then "one" else "lots")
2009 ^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^
2011 Note that the else part is not an action, it is an attribute of
2012 the :class:`Conditional_Expression` itself.
2014 :attribute kind: Either if or elseif
2015 :type: str
2017 :attribute n_cond: The boolean condition expression
2018 :type: Expression
2020 :attribute n_expr: The value if the condition evaluates to true
2021 :type: Expression
2023 """
2024 def __init__(self, mh, t_kind, n_condition, n_expression):
2025 # lobster-trace: LRM.Conditional_Expression
2026 assert isinstance(mh, Message_Handler)
2027 assert isinstance(t_kind, Token)
2028 assert t_kind.kind == "KEYWORD"
2029 assert t_kind.value in ("if", "elsif")
2030 assert isinstance(n_condition, Expression)
2031 assert isinstance(n_expression, Expression)
2032 super().__init__(t_kind.location)
2033 self.kind = t_kind.value
2034 self.n_cond = n_condition
2035 self.n_expr = n_expression
2036 # lobster-trace: LRM.Conditional_Expression_Types
2037 self.n_cond.ensure_type(mh, Builtin_Boolean)
2039 def dump(self, indent=0): # pragma: no cover
2040 # lobster-exclude: Debugging feature
2041 self.write_indent(indent, f"{self.kind.capitalize()} Action")
2042 self.write_indent(indent + 1, "Condition")
2043 self.n_cond.dump(indent + 2)
2044 self.write_indent(indent + 1, "Value")
2045 self.n_expr.dump(indent + 2)
2047 def to_string(self):
2048 return "%s %s then %s" % (self.kind,
2049 self.n_cond.to_string(),
2050 self.n_expr.to_string())
2053class Conditional_Expression(Expression):
2054 """A conditional expression
2056 Each :class:`Conditional_Expression` is made up of a sequence of
2057 one or more :class:`Action`. For example here is a single
2058 expression with two Actions::
2060 (if x == 0 then "zero" elsif x == 1 then "one" else "lots")
2061 ^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^
2063 The else expression is part of the conditional expression itself.
2065 A conditional expression will have at least one action (the if
2066 action), and all other actions will be elsif actions. The else
2067 expression is not optional and will always be present. The types
2068 of all actions and the else expression will match.
2070 :attribute actions: a list of Actions
2071 :type: list[Action]
2073 :attribute else_expr: the else expression
2074 :type: Expression
2076 """
2077 def __init__(self, location, if_action):
2078 # lobster-trace: LRM.Conditional_Expression
2079 assert isinstance(if_action, Action)
2080 assert if_action.kind == "if"
2081 super().__init__(location, if_action.n_expr.typ)
2082 self.actions = [if_action]
2083 self.else_expr = None
2085 def add_elsif(self, mh, n_action):
2086 # lobster-trace: LRM.Conditional_Expression
2087 # lobster-trace; LRM.Conditional_Expression_Types
2088 assert isinstance(mh, Message_Handler)
2089 assert isinstance(n_action, Action)
2090 assert n_action.kind == "elsif"
2092 n_action.n_expr.ensure_type(mh, self.typ)
2093 self.actions.append(n_action)
2095 def set_else_part(self, mh, n_expr):
2096 # lobster-trace: LRM.Conditional_Expression
2097 # lobster-trace; LRM.Conditional_Expression_Types
2098 assert isinstance(mh, Message_Handler)
2099 assert isinstance(n_expr, Expression)
2101 n_expr.ensure_type(mh, self.typ)
2102 self.else_expr = n_expr
2104 def dump(self, indent=0): # pragma: no cover
2105 # lobster-exclude: Debugging feature
2106 self.write_indent(indent, "Conditional expression")
2107 for action in self.actions:
2108 action.dump(indent + 1)
2109 self.write_indent(indent + 1, "Else")
2110 self.else_expr.dump(indent + 2)
2112 def to_string(self):
2113 rv = "(" + " ".join(action.to_string()
2114 for action in self.actions)
2115 rv += " else %s" % self.else_expr.to_string()
2116 rv += ")"
2117 return rv
2119 def evaluate(self, mh, context, gstab):
2120 # lobster-trace: LRM.Conditional_Expression_Else
2121 # lobster-trace: LRM.Conditional_Expression_Evaluation
2122 # lobster-trace: LRM.Null_Is_Invalid
2123 assert isinstance(mh, Message_Handler)
2124 assert context is None or isinstance(context, dict)
2125 assert gstab is None or isinstance(gstab, Symbol_Table)
2127 for action in self.actions:
2128 v_cond = action.n_cond.evaluate(mh, context, gstab)
2129 if v_cond.value is None: 2129 ↛ 2130line 2129 didn't jump to line 2130 because the condition on line 2129 was never true
2130 mh.error(v_cond.location,
2131 "condition of %s (%s) must not be null" %
2132 (action.to_string(),
2133 mh.cross_file_reference(self.location)))
2134 if v_cond.value:
2135 return action.n_expr.evaluate(mh, context, gstab)
2137 return self.else_expr.evaluate(mh, context, gstab)
2139 def can_be_null(self):
2140 if self.else_expr and self.else_expr.can_be_null():
2141 return True
2143 return any(action.n_expr.can_be_null()
2144 for action in self.actions)
2147class Quantified_Expression(Expression):
2148 """A quantified expression
2150 For example::
2152 (forall x in array_component => x > 0)
2153 ^4 ^1 ^2 ^^^^^3
2155 A quantified expression introduces and binds a
2156 :class:`Quantified_Variable` (see 1) from a specified source (see
2157 2). When the body (see 3) is evaluated, the name of 1 is bound to
2158 each component of the source in turn.
2160 :attribute n_var: The quantified variable (see 1)
2161 :type: Quantified_Variable
2163 :attribute n_source: The array to iterate over (see 2)
2164 :type: Name_Reference
2166 :attribute n_expr: The body of the quantifier (see 3)
2167 :type: Expression
2169 :attribute universal: True means forall, false means exists (see 4)
2170 :type: Boolean
2172 """
2173 def __init__(self, mh, location,
2174 typ,
2175 universal,
2176 n_variable,
2177 n_source,
2178 n_expr):
2179 # lobster-trace: LRM.Quantified_Expression
2180 # lobster-trace: LRM.Quantification_Type
2181 super().__init__(location, typ)
2182 assert isinstance(typ, Builtin_Boolean)
2183 assert isinstance(universal, bool)
2184 assert isinstance(n_variable, Quantified_Variable)
2185 assert isinstance(n_expr, Expression)
2186 assert isinstance(n_source, Name_Reference)
2187 self.universal = universal
2188 self.n_var = n_variable
2189 self.n_expr = n_expr
2190 self.n_source = n_source
2191 self.n_expr.ensure_type(mh, Builtin_Boolean)
2193 def dump(self, indent=0): # pragma: no cover
2194 # lobster-exclude: Debugging feature
2195 if self.universal:
2196 self.write_indent(indent, "Universal quantified expression")
2197 else:
2198 self.write_indent(indent, "Existential quantified expression")
2199 self.n_var.dump(indent + 1)
2200 self.n_expr.dump(indent + 1)
2202 def to_string(self):
2203 return "(%s %s in %s => %s)" % ("forall"
2204 if self.universal
2205 else "exists",
2206 self.n_var.name,
2207 self.n_source.to_string(),
2208 self.n_expr.to_string())
2210 def evaluate(self, mh, context, gstab):
2211 # lobster-trace: LRM.Null_Is_Invalid
2212 # lobster-trace: LRM.Universal_Quantification_Semantics
2213 # lobster-trace: LRM.Existential_Quantification_Semantics
2214 assert isinstance(mh, Message_Handler)
2215 assert context is None or isinstance(context, dict)
2216 assert gstab is None or isinstance(gstab, Symbol_Table)
2218 if context is None: 2218 ↛ 2219line 2218 didn't jump to line 2219 because the condition on line 2218 was never true
2219 new_ctx = {}
2220 else:
2221 new_ctx = copy(context)
2223 # This is going to be a bit tricky. We essentially eliminate
2224 # the quantifier and substitute; for the sake of making better
2225 # error messages.
2226 assert isinstance(self.n_source.entity, Composite_Component)
2227 array_values = context[self.n_source.entity.name]
2228 if isinstance(array_values, Implicit_Null):
2229 mh.error(array_values.location,
2230 "%s in quantified expression %s (%s) "
2231 "must not be null" %
2232 (self.n_source.to_string(),
2233 self.to_string(),
2234 mh.cross_file_reference(self.location)))
2235 else:
2236 assert isinstance(array_values, Array_Aggregate)
2238 rv = self.universal
2239 loc = self.location
2240 for binding in array_values.value:
2241 new_ctx[self.n_var.name] = binding
2242 result = self.n_expr.evaluate(mh, new_ctx, gstab)
2243 assert isinstance(result.value, bool)
2244 if self.universal and not result.value:
2245 rv = False
2246 loc = binding.location
2247 break
2248 elif not self.universal and result.value:
2249 rv = True
2250 loc = binding.location
2251 break
2253 return Value(location = loc,
2254 value = rv,
2255 typ = self.typ)
2257 def can_be_null(self):
2258 return False
2261##############################################################################
2262# AST Nodes (Entities)
2263##############################################################################
2265class Entity(Node, metaclass=ABCMeta):
2266 """Base class for all entities.
2268 An entity is a concrete object (with a name) for which we need to
2269 allocate memory. Examples of entities are types and record
2270 objects.
2272 :attribute name: unqualified name of the entity
2273 :type: str
2275 """
2276 def __init__(self, name, location):
2277 # lobster-trace: LRM.Described_Name_Equality
2278 super().__init__(location)
2279 assert isinstance(name, str)
2280 self.name = name
2283class Typed_Entity(Entity, metaclass=ABCMeta):
2284 """Base class for entities with a type.
2286 A typed entity is a concrete object (with a name and TRLC type)
2287 for which we need to allocate memory. Examples of typed entities
2288 are record objects and components.
2290 :attribute n_typ: type of the entity
2291 :type: Type
2293 """
2294 def __init__(self, name, location, n_typ):
2295 # lobster-exclude: Constructor only declares variables
2296 super().__init__(name, location)
2297 assert isinstance(n_typ, Type)
2298 self.n_typ = n_typ
2301class Quantified_Variable(Typed_Entity):
2302 """Variable used in quantified expression.
2304 A quantified expression declares and binds a variable, for which
2305 we need a named entity. For example in::
2307 (forall x in array => x > 1)
2308 ^
2310 We represent this first x as a :class:`Quantified_Variable`, the
2311 second x will be an ordinary :class:`Name_Reference`.
2313 :attribute typ: type of the variable (i.e. element type of the array)
2314 :type: Type
2316 """
2317 def dump(self, indent=0): # pragma: no cover
2318 # lobster-exclude: Debugging feature
2319 self.write_indent(indent, f"Quantified Variable {self.name}")
2320 self.n_typ.dump(indent + 1)
2323class Type(Entity, metaclass=ABCMeta):
2324 """Abstract base class for all types.
2326 """
2327 def perform_type_checks(self, mh, value, gstab):
2328 assert isinstance(mh, Message_Handler)
2329 assert isinstance(value, Expression)
2330 assert isinstance(gstab, Symbol_Table)
2331 return True
2333 def get_example_value(self):
2334 # lobster-exclude: utility method
2335 assert False
2338class Concrete_Type(Type, metaclass=ABCMeta):
2339 # lobster-trace: LRM.Type_Declarations
2340 """Abstract base class for all non-anonymous types.
2342 :attribute n_package: package where this type was declared
2343 :type: Package
2344 """
2345 def __init__(self, name, location, n_package):
2346 super().__init__(name, location)
2347 assert isinstance(n_package, Package)
2348 self.n_package = n_package
2350 def fully_qualified_name(self):
2351 """Return the FQN for this type (i.e. PACKAGE.NAME)
2353 :returns: the type's full name
2354 :rtype: str
2355 """
2356 return self.n_package.name + "." + self.name
2358 def __hash__(self):
2359 return hash((self.n_package.name, self.name))
2361 def __repr__(self):
2362 return "%s<%s>" % (self.__class__.__name__,
2363 self.fully_qualified_name())
2366class Builtin_Type(Type, metaclass=ABCMeta):
2367 # lobster-trace: LRM.Builtin_Types
2368 """Abstract base class for all builtin types.
2370 """
2371 LOCATION = Location(file_name = "<builtin>")
2373 def __init__(self, name):
2374 super().__init__(name, Builtin_Type.LOCATION)
2376 def dump(self, indent=0): # pragma: no cover
2377 self.write_indent(indent, self.__class__.__name__)
2380class Builtin_Numeric_Type(Builtin_Type, metaclass=ABCMeta):
2381 # lobster-trace: LRM.Builtin_Types
2382 """Abstract base class for all builtin numeric types.
2384 """
2385 def dump(self, indent=0): # pragma: no cover
2386 self.write_indent(indent, self.__class__.__name__)
2389class Builtin_Function(Entity):
2390 # lobster-trace: LRM.Builtin_Functions
2391 """Builtin functions.
2393 These are auto-generated by the :class:`~trlc.trlc.Source_Manager`.
2395 :attribute arity: number of parameters
2396 :type: int
2398 :attribute arity_at_least: when true, arity indicates a lower bound
2399 :type: bool
2401 """
2402 LOCATION = Location(file_name = "<builtin>")
2404 def __init__(self, name, arity, arity_at_least=False):
2405 super().__init__(name, Builtin_Function.LOCATION)
2406 assert isinstance(arity, int)
2407 assert isinstance(arity_at_least, bool)
2408 assert arity >= 0
2409 self.arity = arity
2410 self.arity_at_least = arity_at_least
2412 def dump(self, indent=0): # pragma: no cover
2413 self.write_indent(indent, self.__class__.__name__ + " " + self.name)
2416class Array_Type(Type):
2417 """Anonymous array type.
2419 These are declared implicitly for each record component that has
2420 an array specifier::
2422 foo Integer [5 .. *]
2423 ^
2425 :attribute lower_bound: minimum number of elements
2426 :type: int
2428 :attribute loc_lower: text location of the lower bound indicator
2429 :type: Location
2431 :attribute upper_bound: maximum number of elements (or None)
2432 :type: int
2434 :attribute loc_upper: text location of the upper bound indicator
2435 :type: Location
2437 :attribute element_type: type of the array elements
2438 :type: Type
2440 """
2441 def __init__(self,
2442 location,
2443 element_type,
2444 loc_lower,
2445 lower_bound,
2446 loc_upper,
2447 upper_bound):
2448 # lobster-exclude: Constructor only declares variables
2449 assert isinstance(element_type, Type) or element_type is None
2450 assert isinstance(lower_bound, int)
2451 assert lower_bound >= 0
2452 assert upper_bound is None or isinstance(upper_bound, int)
2453 assert upper_bound is None or upper_bound >= 0
2454 assert isinstance(loc_lower, Location)
2455 assert isinstance(loc_upper, Location)
2457 if element_type is None: 2457 ↛ 2458line 2457 didn't jump to line 2458 because the condition on line 2457 was never true
2458 name = "universal array"
2459 elif upper_bound is None:
2460 if lower_bound == 0:
2461 name = "array of %s" % element_type.name
2462 else:
2463 name = "array of at least %u %s" % (lower_bound,
2464 element_type.name)
2465 elif lower_bound == upper_bound:
2466 name = "array of %u %s" % (lower_bound,
2467 element_type.name)
2468 else:
2469 name = "array of %u to %u %s" % (lower_bound,
2470 upper_bound,
2471 element_type.name)
2472 super().__init__(name, location)
2473 self.lower_bound = lower_bound
2474 self.loc_lower = loc_lower
2475 self.upper_bound = upper_bound
2476 self.loc_upper = loc_upper
2477 self.element_type = element_type
2479 def dump(self, indent=0): # pragma: no cover
2480 # lobster-exclude: Debugging feature
2481 self.write_indent(indent, "Array_Type")
2482 self.write_indent(indent + 1, f"Lower bound: {self.lower_bound}")
2483 if self.upper_bound is None:
2484 self.write_indent(indent + 1, "Upper bound: *")
2485 else:
2486 self.write_indent(indent + 1, f"Upper bound: {self.upper_bound}")
2487 self.write_indent(indent + 1, f"Element type: {self.element_type.name}")
2489 def perform_type_checks(self, mh, value, gstab):
2490 assert isinstance(mh, Message_Handler)
2491 assert isinstance(gstab, Symbol_Table)
2493 if isinstance(value, Array_Aggregate):
2494 return all(self.element_type.perform_type_checks(mh, v, gstab)
2495 for v in value.value)
2496 else:
2497 assert isinstance(value, Implicit_Null)
2498 return True
2500 def get_example_value(self):
2501 # lobster-exclude: utility method
2502 return "[%s]" % self.element_type.get_example_value()
2505class Union_Type(Type):
2506 # lobster-trace: LRM.union_type
2507 # lobster-trace: LRM.Union_Type_Minimum_Members
2508 # lobster-trace: LRM.Union_Type_Record_Types_Only
2509 """Anonymous union type for record references.
2511 These are declared implicitly when a record component specifies
2512 multiple allowed record types using bracket syntax::
2514 parent [Systemrequirement, Codebeamerrequirement]
2515 ^
2517 :attribute types: the allowed record types
2518 :type: list[Record_Type]
2520 """
2521 def __init__(self, location, types):
2522 assert isinstance(types, list)
2523 assert len(types) >= 1
2524 assert all(isinstance(t, Record_Type) for t in types)
2525 name = "[%s]" % ", ".join(t.name for t in types)
2526 super().__init__(name, location)
2527 self.types = types
2528 self._field_map = None
2530 def get_field_map(self):
2531 # lobster-trace: LRM.Union_Type_Field_Access
2532 """Compute accessible fields across all union members.
2534 Returns a dict mapping field name to a dict with keys:
2536 * ``component``: a representative Composite_Component
2537 * ``n_typ``: the field type (None if conflicting)
2538 * ``count``: how many member types have this field
2539 * ``total``: total number of member types
2540 * ``optional_in_any``: True if optional in at least one member
2542 :rtype: dict[str, dict]
2543 """
2544 if self._field_map is not None:
2545 return self._field_map
2547 field_map = {}
2548 for record_type in self.types:
2549 seen_in_type = set()
2550 for comp in record_type.all_components():
2551 if comp.name in seen_in_type: 2551 ↛ 2552line 2551 didn't jump to line 2552 because the condition on line 2551 was never true
2552 continue
2553 seen_in_type.add(comp.name)
2554 if comp.name not in field_map:
2555 field_map[comp.name] = {
2556 "component" : comp,
2557 "n_typ" : comp.n_typ,
2558 "count" : 1,
2559 "total" : len(self.types),
2560 "optional_in_any" : comp.optional,
2561 }
2562 else:
2563 info = field_map[comp.name]
2564 info["count"] += 1
2565 # Type identity (is) is correct here:
2566 # non-union type objects are structural
2567 # singletons in the symbol table, so
2568 # identity comparison is both correct and
2569 # cheap.
2570 if info["n_typ"] is not comp.n_typ:
2571 info["n_typ"] = None # type conflict
2572 if comp.optional: 2572 ↛ 2573line 2572 didn't jump to line 2573 because the condition on line 2572 was never true
2573 info["optional_in_any"] = True
2575 self._field_map = field_map
2576 return self._field_map
2578 def dump(self, indent=0): # pragma: no cover
2579 # lobster-exclude: Debugging feature
2580 self.write_indent(indent, "Union_Type")
2581 for t in self.types:
2582 self.write_indent(indent + 1, t.name)
2584 def perform_type_checks(self, mh, value, gstab):
2585 # Union types have no checks of their own; type validation
2586 # happens in Record_Reference.resolve_references() via
2587 # is_compatible(). Returning True unconditionally is
2588 # intentional.
2589 assert isinstance(mh, Message_Handler)
2590 assert isinstance(value, Expression)
2591 assert isinstance(gstab, Symbol_Table)
2592 return True
2594 def is_compatible(self, record_type):
2595 """Test if the given record type is accepted by this union.
2597 :param record_type: type to check
2598 :type record_type: Record_Type
2600 :returns: true if the type is or extends one of the union members
2601 :rtype: bool
2602 """
2603 assert isinstance(record_type, Record_Type)
2604 return any(record_type.is_subclass_of(t) for t in self.types)
2606 def get_example_value(self):
2607 # lobster-exclude: utility method
2608 return "%s_instance" % self.types[0].name
2611class Builtin_Integer(Builtin_Numeric_Type):
2612 # lobster-trace: LRM.Builtin_Types
2613 # lobster-trace: LRM.Integer_Values
2614 """Builtin integer type."""
2615 def __init__(self):
2616 super().__init__("Integer")
2618 def get_example_value(self):
2619 # lobster-exclude: utility method
2620 return "100"
2623class Builtin_Decimal(Builtin_Numeric_Type):
2624 # lobster-trace: LRM.Builtin_Types
2625 # lobster-trace: LRM.Decimal_Values
2626 """Builtin decimal type."""
2627 def __init__(self):
2628 super().__init__("Decimal")
2630 def get_example_value(self):
2631 # lobster-exclude: utility method
2632 return "3.14"
2635class Builtin_Boolean(Builtin_Type):
2636 # lobster-trace: LRM.Builtin_Types
2637 # lobster-trace: LRM.Boolean_Values
2638 """Builtin boolean type."""
2639 def __init__(self):
2640 super().__init__("Boolean")
2642 def get_example_value(self):
2643 # lobster-exclude: utility method
2644 return "true"
2647class Builtin_String(Builtin_Type):
2648 # lobster-trace: LRM.Builtin_Types
2649 # lobster-trace: LRM.String_Values
2650 """Builtin string type."""
2651 def __init__(self):
2652 super().__init__("String")
2654 def get_example_value(self):
2655 # lobster-exclude: utility method
2656 return "\"potato\""
2659class Builtin_Markup_String(Builtin_String):
2660 # lobster-trace: LRM.Builtin_Types
2661 # lobster-trace: LRM.Markup_String_Values
2662 """Builtin string type that allows checked references to TRLC
2663 objects.
2664 """
2665 def __init__(self):
2666 super().__init__()
2667 self.name = "Markup_String"
2669 def get_example_value(self):
2670 # lobster-exclude: utility method
2671 return "\"also see [[potato]]\""
2674class Package(Entity):
2675 """Packages.
2677 A package is declared when it is first encountered (in either a
2678 rsl or trlc file). A package contains all symbols declared in it,
2679 both types and record objects. A package is not associated with
2680 just a single file, it can be spread over multiple files.
2682 :attribute declared_late: indicates if this package is declared in a \
2683 trlc file
2684 :type: bool
2686 :attribute symbols: symbol table of the package
2687 :type: Symbol_Table
2689 """
2690 def __init__(self, name, location, builtin_stab, declared_late):
2691 # lobster-exclude: Constructor only declares variables
2692 super().__init__(name, location)
2693 assert isinstance(builtin_stab, Symbol_Table)
2694 assert isinstance(declared_late, bool)
2695 self.symbols = Symbol_Table()
2696 self.symbols.make_visible(builtin_stab)
2697 self.declared_late = declared_late
2699 def dump(self, indent=0): # pragma: no cover
2700 # lobster-exclude: Debugging feature
2701 self.write_indent(indent, f"Package {self.name}")
2702 self.write_indent(indent + 1, f"Declared_Late: {self.declared_late}")
2703 self.symbols.dump(indent + 1, omit_heading=True)
2705 def __repr__(self):
2706 return "%s<%s>" % (self.__class__.__name__,
2707 self.name)
2710class Composite_Type(Concrete_Type, metaclass=ABCMeta):
2711 """Abstract base for record and tuple types, as they share some
2712 functionality.
2714 :attribute components: type components (including inherited if applicable)
2715 :type: Symbol_Table[Composite_Component]
2717 :attribute description: user-supplied description of the type or None
2718 :type: str
2720 :attribute checks: used-defined checks for this type (excluding \
2721 inherited checks)
2722 :type: list[Check]
2724 """
2725 def __init__(self,
2726 name,
2727 description,
2728 location,
2729 package,
2730 inherited_symbols=None):
2731 # lobster-trace: LRM.Described_Name_Description
2732 super().__init__(name, location, package)
2733 assert isinstance(description, str) or description is None
2734 assert isinstance(inherited_symbols, Symbol_Table) or \
2735 inherited_symbols is None
2737 self.components = Symbol_Table(inherited_symbols)
2738 self.description = description
2739 self.checks = []
2741 def add_check(self, n_check):
2742 # lobster-trace: LRM.Check_Evaluation_Order
2743 assert isinstance(n_check, Check)
2744 self.checks.append(n_check)
2746 def iter_checks(self):
2747 # lobster-trace: LRM.Check_Evaluation_Order
2748 yield from self.checks
2750 def all_components(self):
2751 # lobster-exclude: Convenience function
2752 """Convenience function to get a list of all components.
2754 :rtype: list[Composite_Component]
2755 """
2756 return list(self.components.table.values())
2759class Composite_Component(Typed_Entity):
2760 """Component in a record or tuple.
2762 When declaring a composite type, for each component an entity is
2763 declared::
2765 type|tuple T {
2766 foo "blah" optional Boolean
2767 ^1 ^2 ^3 ^4
2769 :attribute description: optional text (see 2) for this component, or None
2770 :type: str
2772 :attribute member_of: a link back to the containing record or tuple; \
2773 for inherited fields this refers back to the original base record type
2774 :type: Composite_Type
2776 :attribute optional: indicates if the component can be null or not (see 3)
2777 :type: bool
2779 """
2781 def __init__(self,
2782 name,
2783 description,
2784 location,
2785 member_of,
2786 n_typ,
2787 optional):
2788 # lobster-trace: LRM.Described_Name_Description
2789 super().__init__(name, location, n_typ)
2790 assert isinstance(description, str) or description is None
2791 assert isinstance(member_of, Composite_Type)
2792 assert isinstance(optional, bool)
2793 self.description = description
2794 self.member_of = member_of
2795 self.optional = optional
2797 def dump(self, indent=0): # pragma: no cover
2798 # lobster-exclude: Debugging feature
2799 self.write_indent(indent, f"Composite_Component {self.name}")
2800 if self.description:
2801 self.write_indent(indent + 1, f"Description: {self.description}")
2802 self.write_indent(indent + 1, f"Optional: {self.optional}")
2803 self.write_indent(indent + 1, f"Type: {self.n_typ.name}")
2805 def __repr__(self):
2806 return "%s<%s>" % (self.__class__.__name__,
2807 self.member_of.fully_qualified_name() + "." +
2808 self.name)
2811class Record_Type(Composite_Type):
2812 """A user-defined record type.
2814 In this example::
2816 type T "optional description of T" extends Root_T {
2817 ^1 ^2 ^3
2819 Note that (1) is part of the :class:`Entity` base, and (2) is part
2820 of the :class:`Composite_Type` base.
2822 :attribute parent: root type or None, indicated by (3) above
2823 :type: Record_Type
2825 :attribute frozen: mapping of frozen components
2826 :type: dict[str, Expression]
2828 :attribute is_final: type is final (i.e. no new components may be declared)
2829 :type: bool
2831 :attribute is_abstract: type is abstract
2832 :type: bool
2834 """
2835 def __init__(self,
2836 name,
2837 description,
2838 location,
2839 package,
2840 n_parent,
2841 is_abstract):
2842 # lobster-exclude: Constructor only declares variables
2843 assert isinstance(n_parent, Record_Type) or n_parent is None
2844 assert isinstance(is_abstract, bool)
2845 super().__init__(name,
2846 description,
2847 location,
2848 package,
2849 n_parent.components if n_parent else None)
2850 self.parent = n_parent
2851 self.frozen = {}
2852 self.is_final = (n_parent.is_final if n_parent else False)
2853 self.is_abstract = is_abstract
2855 def iter_checks(self):
2856 # lobster-trace: LRM.Check_Evaluation_Order
2857 # lobster-trace: LRM.Check_Evaluation_Order_For_Extensions
2858 if self.parent:
2859 yield from self.parent.iter_checks()
2860 yield from self.checks
2862 def dump(self, indent=0): # pragma: no cover
2863 # lobster-exclude: Debugging feature
2864 self.write_indent(indent, f"Record_Type {self.name}")
2865 if self.description:
2866 self.write_indent(indent + 1, f"Description: {self.description}")
2867 if self.parent:
2868 self.write_indent(indent + 1, f"Parent: {self.parent.name}")
2869 self.components.dump(indent + 1, omit_heading=True)
2870 if self.checks:
2871 self.write_indent(indent + 1, "Checks")
2872 for n_check in self.checks:
2873 n_check.dump(indent + 2)
2874 else:
2875 self.write_indent(indent + 1, "Checks: None")
2877 def all_components(self):
2878 """Convenience function to get a list of all components.
2880 :rtype: list[Composite_Component]
2881 """
2882 if self.parent:
2883 return self.parent.all_components() + \
2884 list(self.components.table.values())
2885 else:
2886 return list(self.components.table.values())
2888 def is_subclass_of(self, record_type):
2889 """ Checks if this record type is or inherits from the given type
2891 :param record_type: check if are or extend this type
2892 :type record_type: Record_Type
2894 :returns: true if we are or extend the given type
2895 :rtype: Boolean
2896 """
2897 assert isinstance(record_type, Record_Type)
2899 ptr = self
2900 while ptr:
2901 if ptr is record_type:
2902 return True
2903 else:
2904 ptr = ptr.parent
2905 return False
2907 def is_frozen(self, n_component):
2908 """Test if the given component is frozen.
2910 :param n_component: a composite component of this record type \
2911 (or any of its parents)
2912 :type n_component: Composite_Component
2914 :rtype: bool
2915 """
2916 assert isinstance(n_component, Composite_Component)
2917 if n_component.name in self.frozen:
2918 return True
2919 elif self.parent:
2920 return self.parent.is_frozen(n_component)
2921 else:
2922 return False
2924 def get_freezing_expression(self, n_component):
2925 """Retrieve the frozen value for a frozen component
2927 It is an internal compiler error to call this method with a
2928 component that his not frozen.
2930 :param n_component: a frozen component of this record type \
2931 (or any of its parents)
2932 :type n_component: Composite_Component
2934 :rtype: Expression
2936 """
2937 assert isinstance(n_component, Composite_Component)
2938 if n_component.name in self.frozen: 2938 ↛ 2940line 2938 didn't jump to line 2940 because the condition on line 2938 was always true
2939 return self.frozen[n_component.name]
2940 elif self.parent:
2941 return self.parent.get_freezing_expression(n_component)
2942 else:
2943 assert False
2945 def get_example_value(self):
2946 # lobster-exclude: utility method
2947 return "%s_instance" % self.name
2950class Tuple_Type(Composite_Type):
2951 """A user-defined tuple type.
2953 In this example::
2955 tuple T "optional description of T" {
2956 ^1 ^2
2958 Note that (1) is part of the :class:`Entity` base, and (2) is part
2959 of the :class:`Composite_Type` base.
2961 :attribute separators: list of syntactic separators.
2962 :type: list[Separator]
2964 Note the list of separators will either be empty, or there will be
2965 precisely one less separator than components.
2967 """
2968 def __init__(self, name, description, location, package):
2969 # lobster-trace: LRM.Tuple_Declaration
2970 super().__init__(name,
2971 description,
2972 location,
2973 package)
2974 self.separators = []
2976 def add_separator(self, n_separator):
2977 # lobster-exclude: utility method
2978 assert isinstance(n_separator, Separator)
2979 assert len(self.separators) + 1 == len(self.components.table)
2980 self.separators.append(n_separator)
2982 def iter_separators(self):
2983 """Iterate over all separators"""
2984 # lobster-exclude: utility method
2985 yield from self.separators
2987 def iter_sequence(self):
2988 """Iterate over all components and separators in syntactic order"""
2989 # lobster-exclude: utility method
2990 if self.separators:
2991 for i, n_component in enumerate(self.components.table.values()):
2992 yield n_component
2993 if i < len(self.separators):
2994 yield self.separators[i]
2995 else:
2996 yield from self.components.table.values()
2998 def has_separators(self):
2999 """Returns true if a tuple type requires separators"""
3000 # lobster-exclude: utility method
3001 return bool(self.separators)
3003 def dump(self, indent=0): # pragma: no cover
3004 # lobster-exclude: Debugging feature
3005 self.write_indent(indent, f"Tuple_Type {self.name}")
3006 if self.description:
3007 self.write_indent(indent + 1, f"Description: {self.description}")
3008 self.write_indent(indent + 1, "Fields")
3009 for n_item in self.iter_sequence():
3010 n_item.dump(indent + 2)
3011 if self.checks:
3012 self.write_indent(indent + 1, "Checks")
3013 for n_check in self.checks:
3014 n_check.dump(indent + 2)
3015 else:
3016 self.write_indent(indent + 1, "Checks: None")
3018 def perform_type_checks(self, mh, value, gstab):
3019 # lobster-trace: LRM.Check_Evaluation_Order
3020 assert isinstance(mh, Message_Handler)
3021 assert isinstance(gstab, Symbol_Table)
3023 if isinstance(value, Tuple_Aggregate): 3023 ↛ 3030line 3023 didn't jump to line 3030 because the condition on line 3023 was always true
3024 ok = True
3025 for check in self.iter_checks():
3026 if not check.perform(mh, value, gstab): 3026 ↛ 3027line 3026 didn't jump to line 3027 because the condition on line 3026 was never true
3027 ok = False
3028 return ok
3029 else:
3030 assert isinstance(value, Implicit_Null)
3031 return True
3033 def get_example_value(self):
3034 # lobster-exclude: utility method
3035 parts = []
3036 for n_item in self.iter_sequence():
3037 if isinstance(n_item, Composite_Component):
3038 parts.append(n_item.n_typ.get_example_value())
3039 else:
3040 parts.append(n_item.to_string())
3041 if self.has_separators():
3042 return " ".join(parts)
3043 else:
3044 return "(%s)" % ", ".join(parts)
3047class Separator(Node):
3048 # lobster-trace: LRM.Tuple_Declaration
3049 """User-defined syntactic separator
3051 For example::
3053 separator x
3054 ^1
3056 :attribute token: token used to separate fields of the tuple
3057 :type: Token
3058 """
3059 def __init__(self, token):
3060 super().__init__(token.location)
3061 assert isinstance(token, Token) and token.kind in ("IDENTIFIER",
3062 "AT",
3063 "COLON",
3064 "SEMICOLON")
3065 self.token = token
3067 def to_string(self):
3068 return {
3069 "AT" : "@",
3070 "COLON" : ":",
3071 "SEMICOLON" : ";"
3072 }.get(self.token.kind, self.token.value)
3074 def dump(self, indent=0): # pragma: no cover
3075 self.write_indent(indent, f"Separator {self.token.value}")
3078class Enumeration_Type(Concrete_Type):
3079 """User-defined enumeration types.
3081 For example::
3083 enum T "potato" {
3084 ^1 ^2
3086 :attribute description: user supplied optional description, or None
3087 :type: str
3089 :attribute literals: the literals in this enumeration
3090 :type: Symbol_Table[Enumeration_Literal_Spec]
3092 """
3093 def __init__(self, name, description, location, package):
3094 # lobster-trace: LRM.Described_Name_Description
3095 super().__init__(name, location, package)
3096 assert isinstance(description, str) or description is None
3097 self.literals = Symbol_Table()
3098 self.description = description
3100 def dump(self, indent=0): # pragma: no cover
3101 # lobster-exclude: Debugging feature
3102 self.write_indent(indent, f"Enumeration_Type {self.name}")
3103 if self.description:
3104 self.write_indent(indent + 1, f"Description: {self.description}")
3105 self.literals.dump(indent + 1, omit_heading=True)
3107 def get_example_value(self):
3108 # lobster-exclude: utility method
3109 options = list(self.literals.values())
3110 if options:
3111 choice = len(options) // 2
3112 return self.name + "." + choice.name
3113 else:
3114 return "ERROR"
3117class Enumeration_Literal_Spec(Typed_Entity):
3118 """Declared literal in an enumeration declaration.
3120 Note that for literals mentioned later in record object
3121 declarations, we use :class:`Enumeration_Literal`. Literal specs
3122 are used here::
3124 enum ASIL {
3125 QM "not safety related"
3126 ^1 ^2
3128 :attribute description: the optional user-supplied description, or None
3129 :type: str
3131 """
3132 def __init__(self, name, description, location, enum):
3133 # lobster-trace: LRM.Described_Name_Description
3134 super().__init__(name, location, enum)
3135 assert isinstance(description, str) or description is None
3136 assert isinstance(enum, Enumeration_Type)
3137 self.description = description
3139 def dump(self, indent=0): # pragma: no cover
3140 # lobster-exclude: Debugging feature
3141 self.write_indent(indent, f"Enumeration_Literal_Spec {self.name}")
3142 if self.description:
3143 self.write_indent(indent + 1, f"Description: {self.description}")
3146class Record_Object(Typed_Entity):
3147 """A declared instance of a record type.
3149 This is going to be the bulk of all entities created by TRLC::
3151 section "Potato" {
3152 ^5
3153 Requirement PotatoReq {
3154 ^1 ^2
3155 component1 = 42
3156 ^3 ^4
3158 Note that the name (see 2) and type (see 1) of the object is
3159 provided by the name attribute of the :class:`Typed_Entity` base
3160 class.
3162 :attribute field: the specific values for all components (see 3 and 4)
3163 :type: dict[str, Expression]
3165 :attribute section: None or the section this record is contained in (see 5)
3166 :type: Section
3168 :attribute n_package: The package in which this record is declared in
3169 :type: Section
3171 The actual type of expressions in the field attribute are limited
3172 to:
3174 * :class:`Literal`
3175 * :class:`Unary_Expression`
3176 * :class:`Array_Aggregate`
3177 * :class:`Tuple_Aggregate`
3178 * :class:`Record_Reference`
3179 * :class:`Implicit_Null`
3181 """
3182 def __init__(self, name, location, n_typ, section, n_package):
3183 # lobster-trace: LRM.Section_Declaration
3184 # lobster-trace: LRM.Unspecified_Optional_Components
3185 # lobster-trace: LRM.Record_Object_Declaration
3187 assert isinstance(n_typ, Record_Type)
3188 assert isinstance(section, list) or section is None
3189 assert isinstance(n_package, Package)
3190 super().__init__(name, location, n_typ)
3191 self.field = {
3192 comp.name: Implicit_Null(self, comp)
3193 for comp in self.n_typ.all_components()
3194 }
3195 self.section = section
3196 self.n_package = n_package
3198 def fully_qualified_name(self):
3199 """Return the FQN for this type (i.e. PACKAGE.NAME)
3201 :returns: the object's full name
3202 :rtype: str
3203 """
3204 return self.n_package.name + "." + self.name
3206 def to_python_dict(self):
3207 """Return an evaluated and simplified object for Python.
3209 For example it might provide::
3211 {"foo" : [1, 2, 3],
3212 "bar" : None,
3213 "baz" : "value"}
3215 This is a function especially designed for the Python API. The
3216 name of the object itself is not in this returned dictionary.
3218 """
3219 return {name: value.to_python_object()
3220 for name, value in self.field.items()}
3222 def is_component_implicit_null(self, component) -> bool:
3223 return not isinstance(self.field[component.name], Implicit_Null)
3225 def assign(self, component, value):
3226 assert isinstance(component, Composite_Component)
3227 assert isinstance(value, (Literal,
3228 Array_Aggregate,
3229 Tuple_Aggregate,
3230 Record_Reference,
3231 Implicit_Null,
3232 Unary_Expression)), \
3233 "value is %s" % value.__class__.__name__
3234 if self.is_component_implicit_null(component): 3234 ↛ 3235line 3234 didn't jump to line 3235 because the condition on line 3234 was never true
3235 raise KeyError(f"Component {component.name} already \
3236 assigned to {self.n_typ.name} {self.name}!")
3237 self.field[component.name] = value
3239 def dump(self, indent=0): # pragma: no cover
3240 # lobster-exclude: Debugging feature
3241 self.write_indent(indent, f"Record_Object {self.name}")
3242 self.write_indent(indent + 1, f"Type: {self.n_typ.name}")
3243 for key, value in self.field.items():
3244 self.write_indent(indent + 1, f"Field {key}")
3245 value.dump(indent + 2)
3246 if self.section:
3247 self.section[-1].dump(indent + 1)
3249 def resolve_references(self, mh):
3250 assert isinstance(mh, Message_Handler)
3251 for val in self.field.values():
3252 val.resolve_references(mh)
3254 def perform_checks(self, mh, gstab):
3255 # lobster-trace: LRM.Check_Evaluation_Order
3256 # lobster-trace: LRM.Evaluation_Of_Checks
3257 assert isinstance(mh, Message_Handler)
3258 assert isinstance(gstab, Symbol_Table)
3260 ok = True
3262 # First evaluate all tuple checks
3263 for n_comp in self.n_typ.all_components():
3264 if not n_comp.n_typ.perform_type_checks(mh, 3264 ↛ 3267line 3264 didn't jump to line 3267 because the condition on line 3264 was never true
3265 self.field[n_comp.name],
3266 gstab):
3267 ok = False
3269 # TODO: Is there a bug here (a check relies on a tuple check)?
3271 # Then evaluate all record checks
3272 for check in self.n_typ.iter_checks():
3273 # Prints messages, if applicable. Raises exception on
3274 # fatal checks, which causes this to abort.
3275 if not check.perform(mh, self, gstab):
3276 ok = False
3278 return ok
3280 def __repr__(self):
3281 return "%s<%s>" % (self.__class__.__name__,
3282 self.n_package.name + "." +
3283 self.n_typ.name + "." +
3284 self.name)
3287class Section(Entity):
3288 # lobster-trace: LRM.Section_Declaration
3289 """A section for readability
3291 This represents a section construct in TRLC files to group record
3292 objects together::
3294 section "Foo" {
3295 ^^^^^ parent section
3296 section "Bar" {
3297 ^^^^^ section
3299 :attribute parent: the parent section or None
3300 :type: Section
3302 """
3303 def __init__(self, name, location, parent):
3304 super().__init__(name, location)
3305 assert isinstance(parent, Section) or parent is None
3306 self.parent = parent
3308 def dump(self, indent=0): # pragma: no cover
3309 self.write_indent(indent, f"Section {self.name}")
3310 if self.parent is None:
3311 self.write_indent(indent + 1, "Parent: None")
3312 else:
3313 self.write_indent(indent + 1, f"Parent: {self.parent.name}")
3316##############################################################################
3317# Symbol Table & Scopes
3318##############################################################################
3320class Symbol_Table:
3321 """ Symbol table mapping names to entities
3322 """
3323 def __init__(self, parent=None):
3324 # lobster-exclude: Constructor only declares variables
3325 assert isinstance(parent, Symbol_Table) or parent is None
3326 self.parent = parent
3327 self.imported = []
3328 self.table = OrderedDict()
3329 self.trlc_files = []
3330 self.section_names = []
3332 @staticmethod
3333 def simplified_name(name):
3334 # lobster-trace: LRM.Sufficiently_Distinct
3335 assert isinstance(name, str)
3336 return name.lower().replace("_", "")
3338 def all_names(self):
3339 # lobster-exclude: API for users
3340 """ All names in the symbol table
3342 :rtype: set[str]
3343 """
3344 rv = set(item.name for item in self.table.values())
3345 if self.parent:
3346 rv |= self.parent.all_names()
3347 return rv
3349 def iter_record_objects_by_section(self):
3350 """API for users
3352 Retriving information about the section hierarchy for record objects
3353 Inputs: folder with trlc files where trlc files have sections,
3354 sub sections and record objects
3355 Output: Information about sections and level of sections,
3356 record objects and levels of record object
3357 """
3358 for record_object in self.iter_record_objects():
3359 location = record_object.location.file_name
3360 if location not in self.trlc_files: 3360 ↛ 3363line 3360 didn't jump to line 3363 because the condition on line 3360 was always true
3361 self.trlc_files.append(location)
3362 yield location
3363 if record_object.section:
3364 object_level = len(record_object.section) - 1
3365 for level, section in enumerate(record_object.section):
3366 if section not in self.section_names: 3366 ↛ 3365line 3366 didn't jump to line 3365 because the condition on line 3366 was always true
3367 self.section_names.append(section)
3368 yield section.name, level
3369 yield record_object, object_level
3370 else:
3371 object_level = 0
3372 yield record_object, object_level
3374 def iter_record_objects(self):
3375 # lobster-exclude: API for users
3376 """ Iterate over all record objects
3378 :rtype: iterable[Record_Object]
3379 """
3380 for item in self.table.values():
3381 if isinstance(item, Package):
3382 yield from item.symbols.iter_record_objects()
3384 elif isinstance(item, Record_Object):
3385 yield item
3387 def values(self, subtype=None):
3388 # lobster-exclude: API for users
3389 assert subtype is None or isinstance(subtype, type)
3390 if self.parent:
3391 yield from self.parent.values(subtype)
3392 for name in sorted(self.table):
3393 if subtype is None or isinstance(self.table[name], subtype):
3394 yield self.table[name]
3396 def make_visible(self, stab):
3397 assert isinstance(stab, Symbol_Table)
3398 self.imported.append(stab)
3400 def register(self, mh, entity):
3401 # lobster-trace: LRM.Duplicate_Types
3402 # lobster-trace: LRM.Unique_Enumeration_Literals
3403 # lobster-trace: LRM.Tuple_Unique_Field_Names
3404 # lobster-trace: LRM.Sufficiently_Distinct
3405 # lobster-trace: LRM.Unique_Object_Names
3407 assert isinstance(mh, Message_Handler)
3408 assert isinstance(entity, Entity)
3410 simple_name = self.simplified_name(entity.name)
3412 if self.contains_raw(simple_name):
3413 pdef = self.lookup_direct(mh, entity.name, entity.location,
3414 simplified=True)
3415 if pdef.name == entity.name:
3416 mh.error(entity.location,
3417 "duplicate definition, previous definition at %s" %
3418 mh.cross_file_reference(pdef.location))
3419 else:
3420 mh.error(entity.location,
3421 "%s is too similar to %s, declared at %s" %
3422 (entity.name,
3423 pdef.name,
3424 mh.cross_file_reference(pdef.location)))
3426 else:
3427 self.table[simple_name] = entity
3429 def __contains__(self, name):
3430 # lobster-trace: LRM.Described_Name_Equality
3431 return self.contains(name)
3433 def contains_raw(self, simple_name, precise_name=None):
3434 # lobster-trace: LRM.Described_Name_Equality
3435 # lobster-trace: LRM.Sufficiently_Distinct
3436 #
3437 # Internal function to test if the simplified name is in the
3438 # table.
3439 assert isinstance(simple_name, str)
3440 assert isinstance(precise_name, str) or precise_name is None
3442 if simple_name in self.table:
3443 # No need to continue searching since registering a
3444 # clashing name would have been stopped
3445 return precise_name is None or \
3446 self.table[simple_name].name == precise_name
3448 elif self.parent:
3449 return self.parent.contains_raw(simple_name, precise_name)
3451 for stab in self.imported:
3452 if stab.contains_raw(simple_name, precise_name):
3453 return True
3455 return False
3457 def contains(self, name):
3458 # lobster-trace: LRM.Described_Name_Equality
3459 """ Tests if the given name is in the table
3461 :param name: the name to test
3462 :type name: str
3464 :rtype: bool
3465 """
3466 assert isinstance(name, str)
3467 return self.contains_raw(self.simplified_name(name), name)
3469 def lookup_assuming(self, mh, name, required_subclass=None):
3470 # lobster-trace: LRM.Described_Name_Equality
3471 # lobster-trace: LRM.Sufficiently_Distinct
3472 """Retrieve an object from the table assuming its there
3474 This is intended for the API specifically where you want to
3475 e.g. find some used-defined types you know are there.
3477 :param mh: The message handler to use
3478 :type mh: Message_Handler
3480 :param name: The name to search for
3481 :type name: str
3483 :param required_subclass: If set, creates an error if the object \
3484 is not an instance of the given class
3485 :type required_subclass: type
3487 :raise TRLC_Error: if the object is not of the required subclass
3488 :returns: the specified entity (or None if it does not exist)
3489 :rtype: Entity
3491 """
3492 assert isinstance(mh, Message_Handler)
3493 assert isinstance(name, str)
3494 assert isinstance(required_subclass, type) or required_subclass is None
3496 simple_name = self.simplified_name(name)
3498 ptr = self
3499 for ptr in [self] + self.imported: 3499 ↛ 3517line 3499 didn't jump to line 3517 because the loop on line 3499 didn't complete
3500 while ptr: 3500 ↛ 3499line 3500 didn't jump to line 3499 because the condition on line 3500 was always true
3501 if simple_name in ptr.table: 3501 ↛ 3515line 3501 didn't jump to line 3515 because the condition on line 3501 was always true
3502 rv = ptr.table[simple_name]
3503 if rv.name != name: 3503 ↛ 3504line 3503 didn't jump to line 3504 because the condition on line 3503 was never true
3504 return None
3506 if required_subclass is not None and \ 3506 ↛ 3508line 3506 didn't jump to line 3508 because the condition on line 3506 was never true
3507 not isinstance(rv, required_subclass):
3508 mh.error(rv.location,
3509 "%s %s is not a %s" %
3510 (rv.__class__.__name__,
3511 name,
3512 required_subclass.__name__))
3513 return rv
3514 else:
3515 ptr = ptr.parent
3517 return None
3519 def lookup_direct(self,
3520 mh,
3521 name,
3522 error_location,
3523 required_subclass=None,
3524 simplified=False):
3525 # lobster-trace: LRM.Described_Name_Equality
3526 # lobster-trace: LRM.Sufficiently_Distinct
3527 # lobster-trace: LRM.Valid_Base_Names
3528 # lobster-trace: LRM.Valid_Access_Prefixes
3529 # lobster-trace: LRM.Valid_Function_Prefixes
3530 """Retrieve an object from the table
3532 For example::
3534 pkg = stab.lookup_direct(mh,
3535 "potato",
3536 Location("foobar.txt", 42),
3537 Package)
3539 This would search for an object named ``potato``. If it is
3540 found, and it is a package, it is returned. If it is not a
3541 Package, then the following error is issued::
3543 foobar.txt:42: error: Enumeration_Type potato is not a Package
3545 If it is not found at all, then the following error is issued::
3547 foobar.txt:42: error: unknown symbol potato
3549 :param mh: The message handler to use
3550 :type mh: Message_Handler
3552 :param name: The name to search for
3553 :type name: str
3555 :param error_location: Where to create the error if the name is \
3556 not found
3557 :type error_location: Location
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 :param simplified: If set, look up the given simplified name instead \
3564 of the actual name
3565 :type simplified: bool
3567 :raise TRLC_Error: if the name is not in the table
3568 :raise TRLC_Error: if the object is not of the required subclass
3569 :returns: the specified entity
3570 :rtype: Entity
3572 """
3573 assert isinstance(mh, Message_Handler)
3574 assert isinstance(name, str)
3575 assert isinstance(error_location, Location)
3576 assert isinstance(required_subclass, type) or required_subclass is None
3577 assert isinstance(simplified, bool)
3579 simple_name = self.simplified_name(name)
3580 ptr = self
3581 options = []
3583 for ptr in [self] + self.imported:
3584 while ptr:
3585 if simple_name in ptr.table:
3586 rv = ptr.table[simple_name]
3587 if not simplified and rv.name != name: 3587 ↛ 3588line 3587 didn't jump to line 3588 because the condition on line 3587 was never true
3588 mh.error(error_location,
3589 "unknown symbol %s, did you mean %s?" %
3590 (name,
3591 rv.name))
3593 if required_subclass is not None and \
3594 not isinstance(rv, required_subclass):
3595 mh.error(error_location,
3596 "%s %s is not a %s" %
3597 (rv.__class__.__name__,
3598 name,
3599 required_subclass.__name__))
3600 return rv
3601 else:
3602 options += list(item.name
3603 for item in ptr.table.values())
3604 ptr = ptr.parent
3606 matches = get_close_matches(
3607 word = name,
3608 possibilities = options,
3609 n = 1)
3611 if matches:
3612 mh.error(error_location,
3613 "unknown symbol %s, did you mean %s?" %
3614 (name,
3615 matches[0]))
3616 else:
3617 mh.error(error_location,
3618 "unknown symbol %s" % name)
3620 def lookup(self, mh, referencing_token, required_subclass=None):
3621 # lobster-trace: LRM.Described_Name_Equality
3622 assert isinstance(mh, Message_Handler)
3623 assert isinstance(referencing_token, Token)
3624 assert referencing_token.kind in ("IDENTIFIER", "BUILTIN")
3625 assert isinstance(required_subclass, type) or required_subclass is None
3627 return self.lookup_direct(
3628 mh = mh,
3629 name = referencing_token.value,
3630 error_location = referencing_token.location,
3631 required_subclass = required_subclass)
3633 def write_indent(self, indent, message): # pragma: no cover
3634 # lobster-exclude: Debugging feature
3635 assert isinstance(indent, int)
3636 assert indent >= 0
3637 assert isinstance(message, str)
3638 print(" " * (3 * indent) + message)
3640 def dump(self, indent=0, omit_heading=False): # pragma: no cover
3641 # lobster-exclude: Debugging feature
3642 if omit_heading:
3643 new_indent = indent
3644 else:
3645 self.write_indent(indent, "Symbol_Table")
3646 new_indent = indent + 1
3647 ptr = self
3648 while ptr:
3649 for name in ptr.table:
3650 ptr.table[name].dump(new_indent)
3651 ptr = ptr.parent
3653 @classmethod
3654 def create_global_table(cls, mh):
3655 # lobster-trace: LRM.Builtin_Types
3656 # lobster-trace: LRM.Builtin_Functions
3657 # lobster-trace: LRM.Builtin_Type_Conversion_Functions
3658 # lobster-trace: LRM.Signature_Len
3659 # lobster-trace: LRM.Signature_String_End_Functions
3660 # lobster-trace: LRM.Signature_Matches
3662 stab = Symbol_Table()
3663 stab.register(mh, Builtin_Integer())
3664 stab.register(mh, Builtin_Decimal())
3665 stab.register(mh, Builtin_Boolean())
3666 stab.register(mh, Builtin_String())
3667 stab.register(mh, Builtin_Markup_String())
3668 stab.register(mh,
3669 Builtin_Function("len", 1))
3670 stab.register(mh,
3671 Builtin_Function("startswith", 2))
3672 stab.register(mh,
3673 Builtin_Function("endswith", 2))
3674 stab.register(mh,
3675 Builtin_Function("matches", 2))
3676 stab.register(mh,
3677 Builtin_Function("oneof", 1, arity_at_least=True))
3679 return stab
3682class Scope:
3683 def __init__(self):
3684 # lobster-exclude: Constructor only declares variables
3685 self.scope = []
3687 def push(self, stab):
3688 assert isinstance(stab, Symbol_Table)
3689 self.scope.append(stab)
3691 def pop(self):
3692 self.scope.pop()
3694 def contains(self, name):
3695 assert isinstance(name, str)
3697 for stab in reversed(self.scope):
3698 if stab.contains(name):
3699 return True
3700 return False
3702 def lookup(self, mh, referencing_token, required_subclass=None):
3703 assert len(self.scope) >= 1
3704 assert isinstance(mh, Message_Handler)
3705 assert isinstance(referencing_token, Token)
3706 assert referencing_token.kind in ("IDENTIFIER", "BUILTIN")
3707 assert isinstance(required_subclass, type) or required_subclass is None
3709 for stab in reversed(self.scope[1:]):
3710 if stab.contains(referencing_token.value):
3711 return stab.lookup(mh, referencing_token, required_subclass)
3712 return self.scope[0].lookup(mh, referencing_token, required_subclass)
3714 def size(self):
3715 return len(self.scope)