Coverage for trlc/parser.py: 96%
1180 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-09-30 11:03 +0000
« prev ^ index » next coverage.py v7.16.2, created at 2026-09-30 11:03 +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) 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/>.
22import re
24from trlc.nested import Nested_Lexer
25from trlc.lexer import Token_Base, Token, Lexer_Base, TRLC_Lexer
26from trlc.errors import Message_Handler, TRLC_Error
27from trlc import ast
30class Markup_Token(Token_Base):
31 # lobster-trace: LRM.Markup_String_Format
33 KIND = {
34 "CHARACTER": "character",
35 "REFLIST_BEGIN": "[[",
36 "REFLIST_END": "]]",
37 "REFLIST_COMMA": ",",
38 "REFLIST_DOT": ".",
39 "REFLIST_IDENTIFIER": "identifier",
40 }
42 def __init__(self, location, kind, value):
43 super().__init__(location, kind, value)
44 assert isinstance(value, str)
47class Markup_Lexer(Nested_Lexer):
48 def __init__(self, mh, literal):
49 super().__init__(mh, literal)
51 self.in_reflist = False
53 def file_location(self):
54 return self.origin_location
56 def token(self):
57 # lobster-trace: LRM.Markup_String_Errors
59 if self.in_reflist:
60 self.skip_whitespace()
61 else:
62 self.advance()
63 if self.cc is None:
64 return None
66 start_pos = self.lexpos
67 start_line = self.line_no
68 start_col = self.col_no
70 if self.cc == "[" and self.nc == "[":
71 kind = "REFLIST_BEGIN"
72 self.advance()
73 if self.in_reflist:
74 self.mh.lex_error(
75 self.source_location(
76 start_line, start_col, start_pos, start_pos + 1
77 ),
78 "cannot nest reference lists",
79 )
80 else:
81 self.in_reflist = True
83 elif self.cc == "]" and self.nc == "]":
84 kind = "REFLIST_END"
85 self.advance()
86 if self.in_reflist:
87 self.in_reflist = False
88 else:
89 self.mh.lex_error(
90 self.source_location(
91 start_line, start_col, start_pos, start_pos + 1
92 ),
93 "opening [[ for this ]] found",
94 )
96 elif not self.in_reflist:
97 kind = "CHARACTER"
99 elif self.cc == ",":
100 kind = "REFLIST_COMMA"
102 elif self.cc == ".":
103 kind = "REFLIST_DOT"
105 elif self.is_alpha(self.cc): 105 ↛ 111line 105 didn't jump to line 111 because the condition on line 105 was always true
106 kind = "REFLIST_IDENTIFIER"
107 while self.nc and (self.is_alnum(self.nc) or self.nc == "_"):
108 self.advance()
110 else:
111 self.mh.lex_error(
112 self.source_location(start_line, start_col, start_pos, start_pos),
113 "unexpected character '%s'" % self.cc,
114 )
116 loc = self.source_location(start_line, start_col, start_pos, self.lexpos)
118 # pylint: disable=possibly-used-before-assignment
119 return Markup_Token(loc, kind, self.content[start_pos : self.lexpos + 1])
122class Parser_Base:
123 def __init__(self, mh, lexer, eoc_name, token_map, keywords):
124 assert isinstance(mh, Message_Handler)
125 assert isinstance(lexer, Lexer_Base)
126 assert isinstance(eoc_name, str)
127 assert isinstance(token_map, dict)
128 assert isinstance(keywords, frozenset)
129 self.mh = mh
130 self.lexer = lexer
132 self.eoc_name = eoc_name
133 self.language_tokens = token_map
134 self.language_keywords = keywords
136 self.ct = None
137 self.nt = None
138 self.advance()
140 def advance(self):
141 # lobster-trace: LRM.Comments
142 self.ct = self.nt
143 while True:
144 self.nt = self.lexer.token()
145 if self.nt is None or self.nt.kind != "COMMENT":
146 break
148 def skip_until_newline(self):
149 if self.ct is None: 149 ↛ 150line 149 didn't jump to line 150 because the condition on line 149 was never true
150 return
151 current_line = self.ct.location.line_no
152 while self.nt and self.nt.location.line_no == current_line:
153 self.advance()
155 def peek(self, kind):
156 assert kind in self.language_tokens, "%s is not a valid token" % kind
157 return self.nt is not None and self.nt.kind == kind
159 def peek_eof(self):
160 return self.nt is None
162 def peek_kw(self, value):
163 assert value in self.language_keywords, "%s is not a valid keyword" % value
164 return self.peek("KEYWORD") and self.nt.value == value
166 def match(self, kind):
167 # lobster-trace: LRM.Matching_Value_Types
169 assert kind in self.language_tokens, "%s is not a valid token" % kind
170 if self.nt is None:
171 if self.ct is None: 171 ↛ 172line 171 didn't jump to line 172 because the condition on line 171 was never true
172 self.mh.error(
173 self.lexer.file_location(),
174 "expected %s, encountered %s instead"
175 % (self.language_tokens[kind], self.eoc_name),
176 )
177 else:
178 self.mh.error(
179 self.ct.location,
180 "expected %s, encountered %s instead"
181 % (self.language_tokens[kind], self.eoc_name),
182 )
183 elif self.nt.kind != kind:
184 self.mh.error(
185 self.nt.location,
186 "expected %s, encountered %s instead"
187 % (self.language_tokens[kind], self.language_tokens[self.nt.kind]),
188 )
189 self.advance()
191 def match_eof(self):
192 if self.nt is not None: 192 ↛ 193line 192 didn't jump to line 193 because the condition on line 192 was never true
193 self.mh.error(
194 self.nt.location,
195 "expected %s, encountered %s instead"
196 % (self.eoc_name, self.language_tokens[self.nt.kind]),
197 )
199 def match_kw(self, value):
200 assert value in self.language_keywords, "%s is not a valid keyword" % value
201 if self.nt is None:
202 if self.ct is None: 202 ↛ 209line 202 didn't jump to line 209 because the condition on line 202 was always true
203 self.mh.error(
204 self.lexer.file_location(),
205 "expected keyword %s, encountered %s instead"
206 % (value, self.eoc_name),
207 )
208 else:
209 self.mh.error(
210 self.ct.location,
211 "expected keyword %s, encountered %s instead"
212 % (value, self.eoc_name),
213 )
214 elif self.nt.kind != "KEYWORD":
215 self.mh.error(
216 self.nt.location,
217 "expected keyword %s, encountered %s instead"
218 % (value, self.language_tokens[self.nt.kind]),
219 )
220 elif self.nt.value != value:
221 self.mh.error(
222 self.nt.location,
223 "expected keyword %s,"
224 " encountered keyword %s instead" % (value, self.nt.value),
225 )
226 self.advance()
229class Markup_Parser(Parser_Base):
230 def __init__(self, parent, literal):
231 assert isinstance(parent, Parser)
232 super().__init__(
233 parent.mh,
234 Markup_Lexer(parent.mh, literal),
235 eoc_name="end-of-string",
236 token_map=Markup_Token.KIND,
237 keywords=frozenset(),
238 )
239 self.parent = parent
240 self.references = literal.references
242 def parse_all_references(self):
243 while self.nt:
244 if self.peek("CHARACTER"):
245 self.advance()
246 else:
247 self.parse_ref_list()
248 self.match_eof()
249 return self.references
251 def parse_ref_list(self):
252 self.match("REFLIST_BEGIN")
253 self.parse_qualified_name()
254 while self.peek("REFLIST_COMMA"):
255 self.match("REFLIST_COMMA")
256 self.parse_qualified_name()
257 self.match("REFLIST_END")
259 def parse_qualified_name(self):
260 # lobster-trace: LRM.Qualified_Name
261 # lobster-trace: LRM.Valid_Qualifier
262 # lobster-trace: LRM.Valid_Name
263 # lobster-trace: LRM.Markup_String_Resolution
264 # lobster-trace: LRM.Markup_String_Types
266 self.match("REFLIST_IDENTIFIER")
267 if self.peek("REFLIST_DOT"):
268 package = self.parent.stab.lookup_direct(
269 mh=self.mh,
270 name=self.ct.value,
271 error_location=self.ct.location,
272 required_subclass=ast.Package,
273 )
274 if not self.parent.cu.is_visible(package):
275 self.mh.error(self.ct.location, "package must be imported before use")
277 self.match("REFLIST_DOT")
278 self.match("REFLIST_IDENTIFIER")
279 else:
280 package = self.parent.cu.package
282 ref = ast.Record_Reference(
283 location=self.ct.location, name=self.ct.value, typ=None, package=package
284 )
285 self.references.append(ref)
288class Parser(Parser_Base):
289 COMPARISON_OPERATOR = ("==", "!=", "<", "<=", ">", ">=")
290 ADDING_OPERATOR = ("+", "-")
291 MULTIPLYING_OPERATOR = ("*", "/", "%")
293 # Keyword that introduces the package indication; overridden by the
294 # markdown parser, where the H1 heading takes this role.
295 PACKAGE_KEYWORD = "package"
297 def __init__(
298 self,
299 mh,
300 stab,
301 file_name,
302 lint_mode,
303 error_recovery,
304 primary_file=True,
305 lexer=None,
306 ):
307 assert isinstance(mh, Message_Handler)
308 assert isinstance(stab, ast.Symbol_Table)
309 assert isinstance(file_name, str)
310 assert isinstance(lint_mode, bool)
311 assert isinstance(error_recovery, bool)
312 assert isinstance(primary_file, bool)
313 assert isinstance(lexer, TRLC_Lexer) or lexer is None
314 if lexer: 314 ↛ 323line 314 didn't jump to line 323 because the condition on line 314 was always true
315 super().__init__(
316 mh,
317 lexer,
318 eoc_name="end-of-file",
319 token_map=Token.KIND,
320 keywords=TRLC_Lexer.KEYWORDS,
321 )
322 else:
323 super().__init__(
324 mh,
325 TRLC_Lexer(mh, file_name),
326 eoc_name="end-of-file",
327 token_map=Token.KIND,
328 keywords=TRLC_Lexer.KEYWORDS,
329 )
331 self.lint_mode = lint_mode
332 self.error_recovery = error_recovery
334 self.stab = stab
335 self.cu = ast.Compilation_Unit(file_name)
337 self.primary = primary_file
338 self.secondary = False
339 # Controls if the file is actually fully parsed: primary means
340 # it was selected on the command-line and secondary means it
341 # was selected by dependency analysis.
343 self.builtin_bool = stab.lookup_assuming(self.mh, "Boolean")
344 self.builtin_int = stab.lookup_assuming(self.mh, "Integer")
345 self.builtin_decimal = stab.lookup_assuming(self.mh, "Decimal")
346 self.builtin_str = stab.lookup_assuming(self.mh, "String")
347 self.builtin_mstr = stab.lookup_assuming(self.mh, "Markup_String")
349 self.section = []
350 self.default_scope = ast.Scope()
351 self.default_scope.push(self.stab)
353 # "rsl" or "trlc"; set by parse_preamble. Remains None only until
354 # parse_preamble runs; code that reads this (e.g.
355 # check_not_self_descendant) is only reachable from parse_rsl_file /
356 # parse_trlc_file, which the Source_Manager only calls on this same
357 # parser instance after its parse_preamble call has completed
358 # successfully, so it is never observed as None there.
359 self.file_kind = None
361 def parse_described_name(self):
362 # lobster-trace: LRM.Described_Names
363 # lobster-trace: LRM.Described_Name_Description
364 self.match("IDENTIFIER")
365 name = self.ct
367 if self.peek("STRING"):
368 self.match("STRING")
369 t_descr = self.ct
370 return name, t_descr.value, t_descr
371 else:
372 return name, None, None
374 def parse_dotted_name(self):
375 """Parse a dotted package name: ``IDENTIFIER { '.' IDENTIFIER }``.
377 :returns: ``(full_name, first_location, tokens)``
378 :rtype: tuple[str, Location, list[Token]]
379 """
380 # lobster-trace: LRM.Nested_Package_Names
381 self.match("IDENTIFIER")
382 parts = [self.ct.value]
383 first_location = self.ct.location
384 tokens = [self.ct]
386 while self.peek("DOT"):
387 self.match("DOT")
388 tokens.append(self.ct)
389 self.match("IDENTIFIER")
390 parts.append(self.ct.value)
391 tokens.append(self.ct)
393 return ".".join(parts), first_location, tokens
395 def parse_import_name(self):
396 """Parse an import target: a dotted package name with an optional
397 trailing ``.*`` wildcard.
399 Consumes ``IDENTIFIER { '.' IDENTIFIER } [ '.' '*' ]``. The ``*``
400 lexes as an ``OPERATOR`` token with value ``"*"``.
402 :returns: ``(full_name, first_location, tokens, is_wildcard)``
403 :rtype: tuple[str, Location, list[Token], bool]
404 """
405 # lobster-trace: LRM.Wildcard_Import
406 # lobster-trace: LRM.Nested_Package_Names
407 self.match("IDENTIFIER")
408 parts = [self.ct.value]
409 first_location = self.ct.location
410 tokens = [self.ct]
411 is_wildcard = False
413 while self.peek("DOT"):
414 self.match("DOT")
415 t_dot = self.ct
416 if self.peek("OPERATOR") and self.nt.value == "*":
417 self.match("OPERATOR")
418 tokens.append(t_dot)
419 tokens.append(self.ct)
420 is_wildcard = True
421 break
422 tokens.append(t_dot)
423 self.match("IDENTIFIER")
424 parts.append(self.ct.value)
425 tokens.append(self.ct)
427 return ".".join(parts), first_location, tokens, is_wildcard
429 def check_not_self_descendant(self, pkg, t_pkg):
430 # lobster-trace: LRM.Self_Descendant_Reference
431 # An rsl file is always elaborated before the rsl files of its
432 # sub-packages, so their declarations do not exist yet. Without
433 # this check the user would get a confusing "unknown symbol"
434 # error for the member instead.
435 if self.file_kind != "rsl":
436 return
437 if not pkg.name.startswith(self.cu.package.name + "."):
438 return
439 self.mh.error(
440 t_pkg.location,
441 "cannot refer to sub-package %s of the current package" % pkg.name,
442 explanation="package %s is elaborated before its sub-packages, "
443 "so %s cannot be used here; move the declarations you need "
444 "into %s itself, or into a package outside the %s subtree"
445 % (
446 self.cu.package.name,
447 pkg.name,
448 self.cu.package.name,
449 self.cu.package.name,
450 ),
451 )
453 def descend_sub_packages(self, pkg, t_pkg):
454 # lobster-trace: LRM.Qualified_Name
455 # lobster-trace: LRM.Nested_Visibility
456 # Greedily consume `. segment` pairs, descending into sub-packages
457 # for as long as a segment names one. The first segment that is not
458 # a sub-package is the member name; we check that the package it
459 # belongs to is visible and mark the import used. Returns
460 # (leaf_package, t_member). If the dotted name ends on a package the
461 # trailing token (self.ct) is returned as the member, so the caller
462 # produces a sensible "unknown symbol" error.
463 #
464 # t_pkg is the token that named the current pkg; it is used as the
465 # location for the "package must be imported" error so the caret
466 # points at the offending package rather than the member.
467 while self.peek("DOT"): 467 ↛ 490line 467 didn't jump to line 490 because the condition on line 467 was always true
468 self.match("DOT")
469 t_dot = self.ct
470 self.match("IDENTIFIER")
471 t_member = self.ct
472 child = pkg.sub_packages.lookup_sub_package(t_member.value)
473 if child is None:
474 pkg.set_ast_link(t_dot)
475 if not self.cu.is_visible(pkg):
476 self.mh.error(
477 t_pkg.location,
478 "package must be imported before use",
479 explanation="add 'import %s' to the "
480 "preamble of this file" % pkg.name,
481 )
482 self.check_not_self_descendant(pkg, t_pkg)
483 self.cu.mark_import_used(pkg)
484 return pkg, t_member
485 child.set_ast_link(t_dot)
486 child.set_ast_link(t_member)
487 pkg = child
488 t_pkg = t_member
490 if not self.cu.is_visible(pkg):
491 self.mh.error(
492 t_pkg.location,
493 "package must be imported before use",
494 explanation="add 'import %s' to the preamble of this file" % pkg.name,
495 )
496 self.check_not_self_descendant(pkg, t_pkg)
497 self.cu.mark_import_used(pkg)
498 return pkg, self.ct
500 def parse_qualified_name(self, scope, required_subclass=None, match_ident=True):
501 # lobster-trace: LRM.Qualified_Name
502 # lobster-trace: LRM.Valid_Qualifier
503 # lobster-trace: LRM.Valid_Name
504 assert isinstance(scope, ast.Scope)
505 assert required_subclass is None or isinstance(required_subclass, type)
506 assert isinstance(match_ident, bool)
508 if match_ident:
509 self.match("IDENTIFIER")
510 sym = scope.lookup(self.mh, self.ct)
511 sym.set_ast_link(self.ct)
513 if isinstance(sym, ast.Package):
514 pkg, t_member = self.descend_sub_packages(sym, self.ct)
515 return pkg.symbols.lookup(self.mh, t_member, required_subclass)
516 else:
517 # Easiest way to generate the correct error message
518 return scope.lookup(self.mh, self.ct, required_subclass)
520 def parse_type_declaration(self):
521 # lobster-trace: LRM.Type_Declarations
522 if self.peek_kw("enum"):
523 n_item = self.parse_enum_declaration()
524 elif self.peek_kw("tuple"):
525 n_item = self.parse_tuple_declaration()
526 else:
527 n_item = self.parse_record_declaration()
528 assert isinstance(n_item, ast.Concrete_Type)
529 return n_item
531 def parse_enum_declaration(self):
532 # lobster-trace: LRM.Enumeration_Declaration
533 self.match_kw("enum")
534 t_enum = self.ct
535 name, description, t_description = self.parse_described_name()
537 enum = ast.Enumeration_Type(
538 name=name.value,
539 description=description,
540 location=name.location,
541 package=self.cu.package,
542 )
543 self.cu.package.symbols.register(self.mh, enum)
544 enum.set_ast_link(t_enum)
545 enum.set_ast_link(name)
546 if t_description:
547 enum.set_ast_link(t_description)
549 self.match("C_BRA")
550 enum.set_ast_link(self.ct)
551 empty = True
552 while not self.peek("C_KET"):
553 name, description, t_description = self.parse_described_name()
554 lit = ast.Enumeration_Literal_Spec(
555 name=name.value,
556 description=description,
557 location=name.location,
558 enum=enum,
559 )
560 lit.set_ast_link(name)
561 if t_description:
562 lit.set_ast_link(self.ct)
563 empty = False
564 enum.literals.register(self.mh, lit)
565 self.match("C_KET")
566 enum.set_ast_link(self.ct)
568 if empty:
569 # lobster-trace: LRM.No_Empty_Enumerations
570 self.mh.error(enum.location, "empty enumerations are not permitted")
572 return enum
574 def parse_tuple_field(
575 self, n_tuple, optional_allowed, optional_reason, optional_required
576 ):
577 assert isinstance(n_tuple, ast.Tuple_Type)
578 assert isinstance(optional_allowed, bool)
579 assert isinstance(optional_reason, str)
580 assert isinstance(optional_required, bool)
581 assert optional_allowed or not optional_required
583 field_name, field_description, t_descr = self.parse_described_name()
585 if optional_required or self.peek_kw("optional"):
586 self.match_kw("optional")
587 t_optional = self.ct
588 field_is_optional = True
589 if not optional_allowed:
590 self.mh.error(self.ct.location, optional_reason)
591 else:
592 field_is_optional = False
593 t_optional = None
595 # lobster-trace: LRM.Tuple_Field_Types
596 # S_BRA here means a union type '[T1, T2, ...]', not array bounds.
597 if self.peek("S_BRA"):
598 # lobster-trace: LRM.union_type
599 field_type = self.parse_union_type()
600 else:
601 field_type = self.parse_qualified_name(self.default_scope, ast.Type)
602 comp = ast.Composite_Component(
603 name=field_name.value,
604 description=field_description,
605 location=field_name.location,
606 member_of=n_tuple,
607 n_typ=field_type,
608 optional=field_is_optional,
609 )
610 comp.set_ast_link(field_name)
611 if t_descr:
612 comp.set_ast_link(t_descr)
613 if field_is_optional:
614 comp.set_ast_link(t_optional)
616 return comp
618 def parse_tuple_declaration(self):
619 # lobster-trace: LRM.Tuple_Declaration
620 self.match_kw("tuple")
621 t_tuple = self.ct
622 name, description, t_descr = self.parse_described_name()
624 n_tuple = ast.Tuple_Type(
625 name=name.value,
626 description=description,
627 location=name.location,
628 package=self.cu.package,
629 )
631 n_tuple.set_ast_link(t_tuple)
632 n_tuple.set_ast_link(name)
633 if t_descr:
634 n_tuple.set_ast_link(t_descr)
635 self.match("C_BRA")
636 n_tuple.set_ast_link(self.ct)
638 n_field = self.parse_tuple_field(
639 n_tuple,
640 optional_allowed=False,
641 optional_reason="first field may not be optional",
642 optional_required=False,
643 )
644 n_tuple.components.register(self.mh, n_field)
646 has_separators = False
647 optional_required = False
648 separator_allowed = True
650 while self.peek_kw("separator") or self.peek("IDENTIFIER"):
651 if has_separators or self.peek_kw("separator"):
652 has_separators = True
653 self.match_kw("separator")
654 t_sep = self.ct
655 if not separator_allowed:
656 # lobster-trace: LRM.Tuple_Separators_All_Or_None
657 self.mh.error(
658 self.ct.location, "either all fields must be separated, or none"
659 )
660 if ( 660 ↛ 674line 660 didn't jump to line 674 because the condition on line 660 was always true
661 self.peek("IDENTIFIER")
662 or self.peek("AT")
663 or self.peek("COLON")
664 or self.peek("SEMICOLON")
665 ):
666 self.advance()
667 sep = ast.Separator(self.ct)
668 sep.set_ast_link(t_sep)
669 sep.set_ast_link(self.ct)
670 n_tuple.add_separator(sep)
671 else:
672 separator_allowed = False
673 # lobster-trace: LRM.Tuple_Optional_Requires_Separators
674 n_field = self.parse_tuple_field(
675 n_tuple,
676 optional_allowed=has_separators,
677 optional_reason=("optional only permitted in tuples with separators"),
678 optional_required=optional_required,
679 )
680 n_tuple.components.register(self.mh, n_field)
681 # lobster-trace: LRM.Tuple_Optional_Fields
682 optional_required |= n_field.optional
684 self.match("C_KET")
685 n_tuple.set_ast_link(self.ct)
687 # Final check to ban tuples with separators containing other
688 # tuples.
689 if has_separators:
690 # lobster-trace: LRM.Restricted_Tuple_Nesting
691 for n_field in n_tuple.components.values():
692 if (
693 isinstance(n_field.n_typ, ast.Tuple_Type)
694 and n_field.n_typ.has_separators()
695 ):
696 self.mh.error(
697 n_field.location,
698 "tuple type %s, which contains separators,"
699 " may not contain another tuple with separators" % n_tuple.name,
700 )
702 # Late registration to avoid recursion in tuples
703 # lobster-trace: LRM.Tuple_Field_Types
704 self.cu.package.symbols.register(self.mh, n_tuple)
706 return n_tuple
708 def parse_union_type(self):
709 """Parse a union type declaration: '[' Type1 ',' Type2 ... ']'
711 The leading S_BRA must be the next token when called.
712 Returns an ast.Union_Type node.
713 """
714 # lobster-trace: LRM.union_type
715 # lobster-trace: LRM.Union_Type_No_Duplicates
716 # lobster-trace: LRM.Union_Type_Record_Types_Only
717 self.match("S_BRA")
718 t_s_bra = self.ct
720 union_type_entries = [] # list of (Record_Type, Location)
721 first_type = self.parse_qualified_name(self.default_scope, ast.Record_Type)
722 first_type.set_ast_link(self.ct)
723 union_type_entries.append((first_type, self.ct.location))
725 while self.peek("COMMA"):
726 self.match("COMMA")
727 next_type = self.parse_qualified_name(self.default_scope, ast.Record_Type)
728 next_type.set_ast_link(self.ct)
729 union_type_entries.append((next_type, self.ct.location))
731 self.match("S_KET")
732 t_s_ket = self.ct
734 seen = {}
735 for t, loc in union_type_entries:
736 fqn = t.fully_qualified_name()
737 if fqn in seen:
738 self.mh.error(loc, "duplicate type %s in union" % t.name, fatal=False)
739 else:
740 seen[fqn] = loc
742 union_types = [t for t, _ in union_type_entries]
743 c_typ = ast.Union_Type(location=t_s_bra.location, types=union_types)
744 c_typ.set_ast_link(t_s_bra)
745 c_typ.set_ast_link(t_s_ket)
746 return c_typ
748 def parse_record_component(self, n_record):
749 assert isinstance(n_record, ast.Record_Type)
751 c_name, c_descr, t_descr = self.parse_described_name()
752 t_optional = None
753 c_optional = False
754 if self.peek_kw("optional"):
755 self.match_kw("optional")
756 t_optional = self.ct
757 c_optional = True
759 # S_BRA here means a union type '[T1, T2, ...]', not array bounds.
760 # Array bounds '[INTEGER..INTEGER]' are checked in the next block.
761 if self.peek("S_BRA"):
762 c_typ = self.parse_union_type()
763 else:
764 c_typ = self.parse_qualified_name(self.default_scope, ast.Type)
765 c_typ.set_ast_link(self.ct)
767 if self.peek("S_BRA"):
768 self.match("S_BRA")
769 t_s_bra = self.ct
770 self.match("INTEGER")
771 t_lo = self.ct
772 a_lo = self.ct.value
773 loc_lo = self.ct.location
774 self.match("RANGE")
775 t_range = self.ct
776 a_loc = self.ct.location
777 a_hi = None
778 if self.peek("INTEGER"):
779 self.match("INTEGER")
780 a_hi = self.ct.value
781 elif self.peek("OPERATOR") and self.nt.value == "*": 781 ↛ 784line 781 didn't jump to line 784 because the condition on line 781 was always true
782 self.match("OPERATOR")
783 else:
784 self.mh.error(self.nt.location, "expected INTEGER or * for upper bound")
785 t_hi = self.ct
786 loc_hi = self.ct.location
787 self.match("S_KET")
788 t_s_ket = self.ct
789 c_typ = ast.Array_Type(
790 location=a_loc,
791 element_type=c_typ,
792 lower_bound=a_lo,
793 upper_bound=a_hi,
794 loc_lower=loc_lo,
795 loc_upper=loc_hi,
796 )
797 c_typ.set_ast_link(t_s_bra)
798 c_typ.set_ast_link(t_lo)
799 c_typ.set_ast_link(t_range)
800 c_typ.set_ast_link(t_hi)
801 c_typ.set_ast_link(t_s_ket)
803 c_comp = ast.Composite_Component(
804 name=c_name.value,
805 description=c_descr,
806 location=c_name.location,
807 member_of=n_record,
808 n_typ=c_typ,
809 optional=c_optional,
810 )
811 c_comp.set_ast_link(c_name)
812 if t_descr:
813 c_comp.set_ast_link(t_descr)
814 if c_optional:
815 c_comp.set_ast_link(t_optional)
817 return c_comp
819 def parse_record_declaration(self):
820 t_abstract = None
821 t_final = None
822 is_abstract = False
823 is_final = False
824 if self.peek_kw("abstract"):
825 self.match_kw("abstract")
826 t_abstract = self.ct
827 is_abstract = True
828 elif self.peek_kw("final"):
829 self.match_kw("final")
830 t_final = self.ct
831 is_final = True
833 self.match_kw("type")
834 t_type = self.ct
835 name, description, t_description = self.parse_described_name()
837 if self.peek_kw("extends"):
838 self.match_kw("extends")
839 t_extends = self.ct
840 root_record = self.parse_qualified_name(self.default_scope, ast.Record_Type)
841 root_record.set_ast_link(t_extends)
842 root_record.set_ast_link(self.ct)
843 else:
844 root_record = None
846 if self.lint_mode and root_record and root_record.is_final and not is_final:
847 self.mh.check(
848 name.location,
849 "consider clarifying that this record is final",
850 "clarify_final",
851 (
852 "Parent record %s is final, making this record\n"
853 "also final. Marking it explicitly as final\n"
854 "clarifies this to casual readers."
855 % root_record.fully_qualified_name()
856 ),
857 )
859 record = ast.Record_Type(
860 name=name.value,
861 description=description,
862 location=name.location,
863 package=self.cu.package,
864 n_parent=root_record,
865 is_abstract=is_abstract,
866 )
867 self.cu.package.symbols.register(self.mh, record)
868 if is_abstract:
869 record.set_ast_link(t_abstract)
870 if is_final:
871 record.set_ast_link(t_final)
872 record.set_ast_link(t_type)
873 record.set_ast_link(name)
874 if t_description:
875 record.set_ast_link(t_description)
877 self.match("C_BRA")
878 record.set_ast_link(self.ct)
879 while not self.peek("C_KET"):
880 if self.peek_kw("freeze"):
881 self.match_kw("freeze")
882 t_freeze = self.ct
883 self.match("IDENTIFIER")
884 n_comp = record.components.lookup(
885 self.mh, self.ct, ast.Composite_Component
886 )
887 if record.is_frozen(n_comp):
888 n_value = record.get_freezing_expression(n_comp)
889 self.mh.error(
890 self.ct.location,
891 "duplicate freezing of %s, previously frozen at %s"
892 % (n_comp.name, self.mh.cross_file_reference(n_value.location)),
893 )
894 n_comp.set_ast_link(t_freeze)
895 n_comp.set_ast_link(self.ct)
896 self.match("ASSIGN")
897 n_comp.set_ast_link(self.ct)
898 n_value = self.parse_value(n_comp.n_typ)
899 n_value.set_ast_link(self.ct)
901 record.frozen[n_comp.name] = n_value
903 else:
904 n_comp = self.parse_record_component(record)
905 if record.is_final:
906 self.mh.error(
907 n_comp.location,
908 "cannot declare new components in final record type",
909 )
910 else:
911 record.components.register(self.mh, n_comp)
913 self.match("C_KET")
914 record.set_ast_link(self.ct)
916 # Finally mark record final if applicable
917 if is_final:
918 record.is_final = True
920 return record
922 def parse_expression(self, scope):
923 # lobster-trace: LRM.Expression
924 assert isinstance(scope, ast.Scope)
926 n_lhs = self.parse_relation(scope)
928 if self.peek_kw("and"):
929 while self.peek_kw("and"):
930 self.match_kw("and")
931 t_op = self.ct
932 a_op = ast.Binary_Operator.LOGICAL_AND
933 t_op.ast_link = a_op
934 n_rhs = self.parse_relation(scope)
935 n_lhs = ast.Binary_Expression(
936 mh=self.mh,
937 location=t_op.location,
938 typ=self.builtin_bool,
939 operator=a_op,
940 n_lhs=n_lhs,
941 n_rhs=n_rhs,
942 )
944 elif self.peek_kw("or"):
945 while self.peek_kw("or"):
946 self.match_kw("or")
947 t_op = self.ct
948 a_op = ast.Binary_Operator.LOGICAL_OR
949 t_op.ast_link = a_op
950 n_rhs = self.parse_relation(scope)
951 n_lhs = ast.Binary_Expression(
952 mh=self.mh,
953 location=t_op.location,
954 typ=self.builtin_bool,
955 operator=a_op,
956 n_lhs=n_lhs,
957 n_rhs=n_rhs,
958 )
960 elif self.peek_kw("xor"):
961 self.match_kw("xor")
962 t_op = self.ct
963 a_op = ast.Binary_Operator.LOGICAL_XOR
964 t_op.ast_link = a_op
965 n_rhs = self.parse_relation(scope)
966 n_lhs = ast.Binary_Expression(
967 mh=self.mh,
968 location=t_op.location,
969 typ=self.builtin_bool,
970 operator=a_op,
971 n_lhs=n_lhs,
972 n_rhs=n_rhs,
973 )
975 elif self.peek_kw("implies"):
976 self.match_kw("implies")
977 t_op = self.ct
978 a_op = ast.Binary_Operator.LOGICAL_IMPLIES
979 t_op.ast_link = a_op
980 n_rhs = self.parse_relation(scope)
981 n_lhs = ast.Binary_Expression(
982 mh=self.mh,
983 location=t_op.location,
984 typ=self.builtin_bool,
985 operator=a_op,
986 n_lhs=n_lhs,
987 n_rhs=n_rhs,
988 )
990 return n_lhs
992 def parse_relation(self, scope):
993 # lobster-trace: LRM.Relation
994 # lobster-trace: LRM.Operators
995 assert isinstance(scope, ast.Scope)
996 relop_mapping = {
997 "==": ast.Binary_Operator.COMP_EQ,
998 "!=": ast.Binary_Operator.COMP_NEQ,
999 "<": ast.Binary_Operator.COMP_LT,
1000 "<=": ast.Binary_Operator.COMP_LEQ,
1001 ">": ast.Binary_Operator.COMP_GT,
1002 ">=": ast.Binary_Operator.COMP_GEQ,
1003 }
1004 assert set(relop_mapping) == set(Parser.COMPARISON_OPERATOR)
1006 n_lhs = self.parse_simple_expression(scope)
1008 if self.peek("OPERATOR") and self.nt.value in Parser.COMPARISON_OPERATOR:
1009 self.match("OPERATOR")
1010 t_op = self.ct
1011 a_op = relop_mapping[t_op.value]
1012 t_op.ast_link = a_op
1013 n_rhs = self.parse_simple_expression(scope)
1014 return ast.Binary_Expression(
1015 mh=self.mh,
1016 location=t_op.location,
1017 typ=self.builtin_bool,
1018 operator=a_op,
1019 n_lhs=n_lhs,
1020 n_rhs=n_rhs,
1021 )
1023 elif self.peek_kw("not") or self.peek_kw("in"):
1024 if self.peek_kw("not"):
1025 self.match_kw("not")
1026 t_not = self.ct
1027 else:
1028 t_not = None
1030 self.match_kw("in")
1031 t_in = self.ct
1033 n_a = self.parse_simple_expression(scope)
1034 t_n_a = self.ct
1035 if self.peek("RANGE"):
1036 self.match("RANGE")
1037 t_range = self.ct
1038 n_b = self.parse_simple_expression(scope)
1039 n_b.set_ast_link(self.ct)
1040 n_a.set_ast_link(t_n_a)
1041 rv = ast.Range_Test(
1042 mh=self.mh,
1043 location=t_in.location,
1044 typ=self.builtin_bool,
1045 n_lhs=n_lhs,
1046 n_lower=n_a,
1047 n_upper=n_b,
1048 )
1049 rv.set_ast_link(t_range)
1050 rv.set_ast_link(t_in)
1052 elif isinstance(n_a.typ, ast.Builtin_String):
1053 rv = ast.Binary_Expression(
1054 mh=self.mh,
1055 location=t_in.location,
1056 typ=self.builtin_bool,
1057 operator=ast.Binary_Operator.STRING_CONTAINS,
1058 n_lhs=n_lhs,
1059 n_rhs=n_a,
1060 )
1061 rv.set_ast_link(t_in)
1063 elif isinstance(n_a.typ, ast.Array_Type): 1063 ↛ 1076line 1063 didn't jump to line 1076 because the condition on line 1063 was always true
1064 a_op = ast.Binary_Operator.ARRAY_CONTAINS
1065 t_in.ast_link = a_op
1066 rv = ast.Binary_Expression(
1067 mh=self.mh,
1068 location=t_in.location,
1069 typ=self.builtin_bool,
1070 operator=a_op,
1071 n_lhs=n_lhs,
1072 n_rhs=n_a,
1073 )
1075 else:
1076 self.mh.error(
1077 n_a.location,
1078 "membership test only defined for Strings and Arrays,"
1079 " not for %s" % n_a.typ.name,
1080 )
1082 if t_not is not None:
1083 a_unary_op = ast.Unary_Operator.LOGICAL_NOT
1084 t_not.ast_link = a_unary_op
1085 rv = ast.Unary_Expression(
1086 mh=self.mh,
1087 location=t_not.location,
1088 typ=self.builtin_bool,
1089 operator=a_unary_op,
1090 n_operand=rv,
1091 )
1093 return rv
1095 else:
1096 return n_lhs
1098 def parse_simple_expression(self, scope):
1099 # lobster-trace: LRM.Simple_Expression
1100 # lobster-trace: LRM.Operators
1101 # lobster-trace: LRM.Unary_Minus_Parsing
1102 assert isinstance(scope, ast.Scope)
1103 un_add_map = {"+": ast.Unary_Operator.PLUS, "-": ast.Unary_Operator.MINUS}
1104 bin_add_map = {"+": ast.Binary_Operator.PLUS, "-": ast.Binary_Operator.MINUS}
1105 assert set(un_add_map) == set(Parser.ADDING_OPERATOR)
1106 assert set(bin_add_map) == set(Parser.ADDING_OPERATOR)
1108 if self.peek("OPERATOR") and self.nt.value in Parser.ADDING_OPERATOR:
1109 self.match("OPERATOR")
1110 t_unary = self.ct
1111 a_unary = un_add_map[t_unary.value]
1112 t_unary.ast_link = a_unary
1113 has_explicit_brackets = self.peek("BRA")
1114 else:
1115 t_unary = None
1117 n_lhs = self.parse_term(scope)
1118 if t_unary:
1119 # pylint: disable=possibly-used-before-assignment
1120 if (
1121 self.lint_mode
1122 and isinstance(n_lhs, ast.Binary_Expression)
1123 and not has_explicit_brackets
1124 ):
1125 self.mh.check(
1126 t_unary.location,
1127 "expression means -(%s), place explicit "
1128 "brackets to clarify intent" % n_lhs.to_string(),
1129 "unary_minus_precedence",
1130 )
1132 n_lhs = ast.Unary_Expression(
1133 mh=self.mh,
1134 location=t_unary.location,
1135 typ=n_lhs.typ,
1136 operator=a_unary,
1137 n_operand=n_lhs,
1138 )
1140 if isinstance(n_lhs.typ, ast.Builtin_String):
1141 rtyp = self.builtin_str
1142 else:
1143 rtyp = n_lhs.typ
1145 while self.peek("OPERATOR") and self.nt.value in Parser.ADDING_OPERATOR:
1146 self.match("OPERATOR")
1147 t_op = self.ct
1148 a_op = bin_add_map[t_op.value]
1149 t_op.ast_link = a_op
1150 n_rhs = self.parse_term(scope)
1151 n_lhs = ast.Binary_Expression(
1152 mh=self.mh,
1153 location=t_op.location,
1154 typ=rtyp,
1155 operator=a_op,
1156 n_lhs=n_lhs,
1157 n_rhs=n_rhs,
1158 )
1160 return n_lhs
1162 def parse_term(self, scope):
1163 # lobster-trace: LRM.Term
1164 # lobster-trace: LRM.Operators
1165 assert isinstance(scope, ast.Scope)
1166 mul_map = {
1167 "*": ast.Binary_Operator.TIMES,
1168 "/": ast.Binary_Operator.DIVIDE,
1169 "%": ast.Binary_Operator.REMAINDER,
1170 }
1171 assert set(mul_map) == set(Parser.MULTIPLYING_OPERATOR)
1173 n_lhs = self.parse_factor(scope)
1174 while self.peek("OPERATOR") and self.nt.value in Parser.MULTIPLYING_OPERATOR:
1175 self.match("OPERATOR")
1176 t_op = self.ct
1177 a_op = mul_map[t_op.value]
1178 t_op.ast_link = a_op
1179 n_rhs = self.parse_factor(scope)
1180 n_lhs = ast.Binary_Expression(
1181 mh=self.mh,
1182 location=t_op.location,
1183 typ=n_lhs.typ,
1184 operator=a_op,
1185 n_lhs=n_lhs,
1186 n_rhs=n_rhs,
1187 )
1189 return n_lhs
1191 def parse_factor(self, scope):
1192 # lobster-trace: LRM.Factor
1193 assert isinstance(scope, ast.Scope)
1195 if self.peek_kw("not"):
1196 self.match_kw("not")
1197 t_op = self.ct
1198 n_operand = self.parse_primary(scope)
1199 a_not = ast.Unary_Operator.LOGICAL_NOT
1200 t_op.ast_link = a_not
1201 return ast.Unary_Expression(
1202 mh=self.mh,
1203 location=t_op.location,
1204 typ=self.builtin_bool,
1205 operator=a_not,
1206 n_operand=n_operand,
1207 )
1209 elif self.peek_kw("abs"):
1210 self.match_kw("abs")
1211 t_op = self.ct
1212 n_operand = self.parse_primary(scope)
1213 a_abs = ast.Unary_Operator.ABSOLUTE_VALUE
1214 t_op.ast_link = a_abs
1215 return ast.Unary_Expression(
1216 mh=self.mh,
1217 location=t_op.location,
1218 typ=n_operand.typ,
1219 operator=a_abs,
1220 n_operand=n_operand,
1221 )
1223 else:
1224 n_lhs = self.parse_primary(scope)
1225 if self.peek("OPERATOR") and self.nt.value == "**":
1226 self.match("OPERATOR")
1227 t_op = self.ct
1228 n_rhs = self.parse_primary(scope)
1229 rhs_value = n_rhs.evaluate(self.mh, None, None)
1230 a_binary = ast.Binary_Operator.POWER
1231 t_op.ast_link = a_binary
1232 n_lhs = ast.Binary_Expression(
1233 mh=self.mh,
1234 location=t_op.location,
1235 typ=n_lhs.typ,
1236 operator=a_binary,
1237 n_lhs=n_lhs,
1238 n_rhs=n_rhs,
1239 )
1240 if rhs_value.value < 0: 1240 ↛ 1241line 1240 didn't jump to line 1241 because the condition on line 1240 was never true
1241 self.mh.error(n_rhs.location, "exponent must not be negative")
1242 return n_lhs
1244 def parse_primary(self, scope):
1245 # lobster-trace: LRM.Primary
1246 assert isinstance(scope, ast.Scope)
1248 if self.peek("INTEGER"):
1249 # lobster-trace: LRM.Integer_Values
1250 self.match("INTEGER")
1251 int_lit = ast.Integer_Literal(self.ct, self.builtin_int)
1252 int_lit.set_ast_link(self.ct)
1253 return int_lit
1255 elif self.peek("DECIMAL"):
1256 # lobster-trace: LRM.Decimal_Values
1257 self.match("DECIMAL")
1258 dec_lit = ast.Decimal_Literal(self.ct, self.builtin_decimal)
1259 dec_lit.set_ast_link(self.ct)
1260 return dec_lit
1262 elif self.peek("STRING"):
1263 # lobster-trace: LRM.String_Values
1264 self.match("STRING")
1265 string_lit = ast.String_Literal(self.ct, self.builtin_str)
1266 string_lit.set_ast_link(self.ct)
1267 return string_lit
1269 elif self.peek_kw("true") or self.peek_kw("false"):
1270 # lobster-trace: LRM.Boolean_Values
1271 self.match("KEYWORD")
1272 bool_lit = ast.Boolean_Literal(self.ct, self.builtin_bool)
1273 bool_lit.set_ast_link(self.ct)
1274 return bool_lit
1276 elif self.peek_kw("null"):
1277 self.match_kw("null")
1278 null_lit = ast.Null_Literal(self.ct)
1279 null_lit.set_ast_link(self.ct)
1280 return null_lit
1282 elif self.peek("BRA"):
1283 self.match("BRA")
1284 t_bra = self.ct
1285 if self.peek_kw("forall") or self.peek_kw("exists"):
1286 rv = self.parse_quantified_expression(scope)
1287 elif self.peek_kw("if"):
1288 rv = self.parse_conditional_expression(scope)
1289 else:
1290 rv = self.parse_expression(scope)
1291 rv.set_ast_link(t_bra)
1292 self.match("KET")
1293 rv.set_ast_link(self.ct)
1294 return rv
1296 else:
1297 return self.parse_name(scope)
1299 def parse_quantified_expression(self, scope):
1300 # lobster-trace: LRM.Quantified_Expression
1301 assert isinstance(scope, ast.Scope)
1303 if self.peek_kw("forall"):
1304 self.match_kw("forall")
1305 t_quantified = self.ct
1306 universal = True
1307 else:
1308 self.match_kw("exists")
1309 t_quantified = self.ct
1310 universal = False
1311 loc = self.ct.location
1312 self.match("IDENTIFIER")
1313 t_qv = self.ct
1314 if scope.contains(t_qv.value):
1315 # lobster-trace: LRM.Quantification_Naming_Scope
1316 pdef = scope.lookup(self.mh, t_qv)
1317 self.mh.error(
1318 t_qv.location,
1319 "shadows %s %s from %s"
1320 % (
1321 pdef.__class__.__name__,
1322 pdef.name,
1323 self.mh.cross_file_reference(pdef.location),
1324 ),
1325 )
1326 self.match_kw("in")
1327 t_in = self.ct
1328 self.match("IDENTIFIER")
1329 field = scope.lookup(self.mh, self.ct, ast.Composite_Component)
1330 n_source = ast.Name_Reference(self.ct.location, field)
1331 n_source.set_ast_link(self.ct)
1332 if not isinstance(field.n_typ, ast.Array_Type):
1333 # lobster-trace: LRM.Quantification_Object
1334 self.mh.error(self.ct.location, "you can only quantify over arrays")
1335 n_var = ast.Quantified_Variable(
1336 t_qv.value, t_qv.location, field.n_typ.element_type
1337 )
1338 n_var.set_ast_link(t_qv)
1339 self.match("ARROW")
1340 t_arrow = self.ct
1342 new_table = ast.Symbol_Table()
1343 new_table.register(self.mh, n_var)
1344 scope.push(new_table)
1345 n_expr = self.parse_expression(scope)
1346 scope.pop()
1348 quantified_expression = ast.Quantified_Expression(
1349 mh=self.mh,
1350 location=loc,
1351 typ=self.builtin_bool,
1352 universal=universal,
1353 n_variable=n_var,
1354 n_source=n_source,
1355 n_expr=n_expr,
1356 )
1358 quantified_expression.set_ast_link(t_quantified)
1359 quantified_expression.set_ast_link(t_in)
1360 quantified_expression.set_ast_link(t_arrow)
1362 return quantified_expression
1364 def parse_conditional_expression(self, scope):
1365 # lobster-trace: LRM.Conditional_Expression
1366 # lobster-trace: LRM.Restricted_Null
1367 assert isinstance(scope, ast.Scope)
1369 self.match_kw("if")
1370 t_if = self.ct
1371 if_cond = self.parse_expression(scope)
1372 self.match_kw("then")
1373 t_then = self.ct
1374 if_expr = self.parse_expression(scope)
1375 if if_expr.typ is None:
1376 self.mh.error(if_expr.location, "null is not permitted here")
1377 if_action = ast.Action(self.mh, t_if, if_cond, if_expr)
1379 rv = ast.Conditional_Expression(location=t_if.location, if_action=if_action)
1380 if_action.set_ast_link(t_if)
1381 if_action.set_ast_link(t_then)
1383 while self.peek_kw("elsif"):
1384 self.match_kw("elsif")
1385 t_elsif = self.ct
1386 elsif_cond = self.parse_expression(scope)
1387 self.match_kw("then")
1388 t_then = self.ct
1389 elsif_expr = self.parse_expression(scope)
1390 elsif_action = ast.Action(self.mh, t_elsif, elsif_cond, elsif_expr)
1391 elsif_action.set_ast_link(t_elsif)
1392 elsif_action.set_ast_link(t_then)
1393 rv.add_elsif(self.mh, elsif_action)
1395 self.match_kw("else")
1396 rv.set_ast_link(self.ct)
1397 else_expr = self.parse_expression(scope)
1398 rv.set_else_part(self.mh, else_expr)
1400 return rv
1402 def parse_builtin(self, scope, n_name, t_name):
1403 # lobster-trace: LRM.Builtin_Functions
1404 # lobster-trace: LRM.Builtin_Type_Conversion_Functions
1405 assert isinstance(scope, ast.Scope)
1406 assert isinstance(n_name, (ast.Builtin_Function, ast.Builtin_Numeric_Type))
1407 assert isinstance(t_name, Token)
1409 # Parse the arguments.
1410 parameters = []
1411 n_name.set_ast_link(self.ct)
1412 self.match("BRA")
1413 n_name.set_ast_link(self.ct)
1414 while not self.peek("KET"): 1414 ↛ 1425line 1414 didn't jump to line 1425 because the condition on line 1414 was always true
1415 exp = self.parse_expression(scope)
1416 if not self.ct.ast_link: 1416 ↛ 1417line 1416 didn't jump to line 1417 because the condition on line 1416 was never true
1417 exp.set_ast_link(self.ct)
1418 parameters.append(exp)
1420 if self.peek("COMMA"):
1421 self.match("COMMA")
1422 n_name.set_ast_link(self.ct)
1423 else:
1424 break
1425 self.match("KET")
1426 n_name.set_ast_link(self.ct)
1428 # Enforce arity
1429 if isinstance(n_name, ast.Builtin_Function):
1430 required_arity = n_name.arity
1431 precise = not n_name.arity_at_least
1432 else:
1433 required_arity = 1
1434 precise = True
1436 if precise:
1437 if required_arity != len(parameters):
1438 self.mh.error(
1439 t_name.location, "function requires %u parameters" % n_name.arity
1440 )
1441 else:
1442 if required_arity > len(parameters): 1442 ↛ 1443line 1442 didn't jump to line 1443 because the condition on line 1442 was never true
1443 self.mh.error(
1444 t_name.location,
1445 "function requires at least %u parameters" % n_name.arity,
1446 )
1448 # Enforce types
1449 if n_name.name == "len":
1450 if isinstance(parameters[0].typ, ast.Builtin_String):
1451 return ast.Unary_Expression(
1452 mh=self.mh,
1453 location=t_name.location,
1454 typ=self.builtin_int,
1455 operator=ast.Unary_Operator.STRING_LENGTH,
1456 n_operand=parameters[0],
1457 )
1458 else:
1459 return ast.Unary_Expression(
1460 mh=self.mh,
1461 location=t_name.location,
1462 typ=self.builtin_int,
1463 operator=ast.Unary_Operator.ARRAY_LENGTH,
1464 n_operand=parameters[0],
1465 )
1467 elif n_name.name in ("startswith", "endswith"):
1468 return ast.Binary_Expression(
1469 mh=self.mh,
1470 location=t_name.location,
1471 typ=self.builtin_bool,
1472 operator=(
1473 ast.Binary_Operator.STRING_STARTSWITH
1474 if "startswith" in n_name.name
1475 else ast.Binary_Operator.STRING_ENDSWITH
1476 ),
1477 n_lhs=parameters[0],
1478 n_rhs=parameters[1],
1479 )
1481 elif n_name.name == "matches":
1482 parameters[1].ensure_type(self.mh, ast.Builtin_String)
1483 try:
1484 # lobster-trace: LRM.Static_Regular_Expression
1485 # scope is None on purpose to enforce static context
1486 value = parameters[1].evaluate(self.mh, None, None)
1487 assert isinstance(value.typ, ast.Builtin_String)
1488 re.compile(value.value)
1489 except re.error as err:
1490 self.mh.error(value.location, str(err))
1491 return ast.Binary_Expression(
1492 mh=self.mh,
1493 location=t_name.location,
1494 typ=self.builtin_bool,
1495 operator=ast.Binary_Operator.STRING_REGEX,
1496 n_lhs=parameters[0],
1497 n_rhs=parameters[1],
1498 )
1500 elif n_name.name == "oneof":
1501 return ast.OneOf_Expression(
1502 mh=self.mh,
1503 location=t_name.location,
1504 typ=self.builtin_bool,
1505 choices=parameters,
1506 )
1508 elif isinstance(n_name, ast.Builtin_Numeric_Type):
1509 parameters[0].ensure_type(self.mh, ast.Builtin_Numeric_Type)
1510 if isinstance(n_name, ast.Builtin_Integer):
1511 return ast.Unary_Expression(
1512 mh=self.mh,
1513 location=t_name.location,
1514 typ=self.builtin_int,
1515 operator=ast.Unary_Operator.CONVERSION_TO_INT,
1516 n_operand=parameters[0],
1517 )
1518 elif isinstance(n_name, ast.Builtin_Decimal):
1519 return ast.Unary_Expression(
1520 mh=self.mh,
1521 location=t_name.location,
1522 typ=self.builtin_decimal,
1523 operator=ast.Unary_Operator.CONVERSION_TO_DECIMAL,
1524 n_operand=parameters[0],
1525 )
1526 else:
1527 self.mh.ice_loc(t_name.location, "unexpected type conversion")
1529 else:
1530 self.mh.ice_loc(t_name.location, "unexpected builtin")
1532 def parse_name(self, scope):
1533 # lobster-trace: LRM.Names
1535 # This is a bit more complex. The grammar is:
1536 #
1537 # qualified_name ::= [ IDENTIFIER_package_name '.' ] IDENTIFIER_name
1538 #
1539 # name ::= qualified_name
1540 # | name '.' IDENTIFIER
1541 # | name '[' expression ']'
1542 # | name '(' parameter_list ')'
1543 #
1544 # parameter_list ::= expression { ',' expression }
1546 assert isinstance(scope, ast.Scope)
1548 # All names start with a (qualified) identifier. We parse that
1549 # first. There is a special complication for functions, as
1550 # builtin functions (e.g. len) can shadow record
1551 # components. However as functions cannot be stored in
1552 # components the true grammar for function calls is always
1553 # IDENTIFIER '('; so we can slightly special case this.
1555 # lobster-trace: LRM.Builtin_Functions
1556 # lobster-trace: LRM.Builtin_Type_Conversion_Functions
1557 self.match("IDENTIFIER")
1558 if self.peek("BRA"):
1559 # If we follow our name with brackets
1560 # immediately, we have a builtin function call.
1561 n_name = self.stab.lookup(self.mh, self.ct)
1562 if not isinstance(n_name, (ast.Builtin_Function, ast.Builtin_Numeric_Type)): 1562 ↛ 1563line 1562 didn't jump to line 1563 because the condition on line 1562 was never true
1563 self.mh.error(
1564 self.ct.location, "not a valid builtin function or numeric type"
1565 )
1566 else:
1567 n_name = self.parse_qualified_name(scope, match_ident=False)
1569 # Enum literals are a bit different, so we deal with them
1570 # first.
1571 if isinstance(n_name, ast.Enumeration_Type):
1572 n_name.set_ast_link(self.ct)
1573 self.match("DOT")
1574 n_name.set_ast_link(self.ct)
1575 self.match("IDENTIFIER")
1576 lit = n_name.literals.lookup(self.mh, self.ct, ast.Enumeration_Literal_Spec)
1577 enum_lit = ast.Enumeration_Literal(location=self.ct.location, literal=lit)
1578 enum_lit.set_ast_link(self.ct)
1579 return enum_lit
1581 # Anything that remains is either a function call or an actual
1582 # name. Let's just enforce this for sanity.
1583 if not isinstance( 1583 ↛ 1592line 1583 didn't jump to line 1592 because the condition on line 1583 was never true
1584 n_name,
1585 (
1586 ast.Builtin_Function,
1587 ast.Builtin_Numeric_Type,
1588 ast.Composite_Component,
1589 ast.Quantified_Variable,
1590 ),
1591 ):
1592 self.mh.error(
1593 self.ct.location,
1594 "%s %s is not a valid name" % (n_name.__class__.__name__, n_name.name),
1595 )
1597 # Right now function calls and type conversions must be
1598 # top-level, so let's get these out of the way as well.
1599 if isinstance(n_name, (ast.Builtin_Function, ast.Builtin_Numeric_Type)):
1600 # lobster-trace: LRM.Builtin_Functions
1601 # lobster-trace: LRM.Builtin_Type_Conversion_Functions
1602 return self.parse_builtin(scope, n_name, self.ct)
1604 assert isinstance(n_name, (ast.Composite_Component, ast.Quantified_Variable))
1606 # We now process the potentially recursive part:
1607 # | name '.' IDENTIFIER
1608 # | name '[' expression ']'
1609 n_name = ast.Name_Reference(location=self.ct.location, entity=n_name)
1610 n_name.set_ast_link(self.ct)
1611 while self.peek("DOT") or self.peek("S_BRA"):
1612 if self.peek("DOT"):
1613 if not isinstance( 1613 ↛ 1617line 1613 didn't jump to line 1617 because the condition on line 1613 was never true
1614 n_name.typ, (ast.Tuple_Type, ast.Record_Type, ast.Union_Type)
1615 ):
1616 # lobster-trace: LRM.Valid_Index_Prefixes
1617 self.mh.error(
1618 n_name.location,
1619 "expression '%s' has type %s, "
1620 "which is not a tuple, record, or union"
1621 % (n_name.to_string(), n_name.typ.name),
1622 )
1624 self.match("DOT")
1625 t_dot = self.ct
1626 self.match("IDENTIFIER")
1627 t_field = self.ct
1629 is_union_access = isinstance(n_name.typ, ast.Union_Type)
1630 is_universal = True
1632 if is_union_access:
1633 # lobster-trace: LRM.Union_Type_Field_Access
1634 # lobster-trace: LRM.Union_Type_Field_Type_Conflict
1635 # lobster-trace: LRM.Union_Type_Field_Access_Validity
1636 field_name = t_field.value
1637 field_map = n_name.typ.get_field_map()
1638 if field_name not in field_map:
1639 self.mh.error(
1640 t_field.location,
1641 "field %s does not exist in any member"
1642 " of union type %s" % (field_name, n_name.typ.name),
1643 )
1644 info = field_map[field_name]
1645 if info["n_typ"] is None:
1646 self.mh.error(
1647 t_field.location,
1648 "field %s has conflicting types in"
1649 " members of union type %s" % (field_name, n_name.typ.name),
1650 )
1651 is_universal = info["count"] == info["total"]
1652 # lobster-trace: LRM.Union_Type_Partial_Field_Access
1653 if self.lint_mode and not is_universal:
1654 self.mh.check(
1655 t_field.location,
1656 "field %s exists only in %u of %u"
1657 " members of union type %s;"
1658 " accessing it on other members"
1659 " returns null"
1660 % (
1661 field_name,
1662 info["count"],
1663 info["total"],
1664 n_name.typ.name,
1665 ),
1666 "union_partial_field_access",
1667 )
1668 n_field = info["component"]
1669 else:
1670 n_field = n_name.typ.components.lookup(
1671 self.mh, t_field, ast.Composite_Component
1672 )
1674 n_field.set_ast_link(t_field)
1675 n_name = ast.Field_Access_Expression(
1676 mh=self.mh,
1677 location=t_field.location,
1678 n_prefix=n_name,
1679 n_field=n_field,
1680 is_union_access=is_union_access,
1681 is_universal=is_universal,
1682 )
1683 n_name.set_ast_link(t_dot)
1685 elif self.peek("S_BRA"): 1685 ↛ 1611line 1685 didn't jump to line 1611 because the condition on line 1685 was always true
1686 if not isinstance(n_name.typ, ast.Array_Type):
1687 self.mh.error(
1688 n_name.location,
1689 "expression '%s' has type %s, "
1690 "which is not an array" % (n_name.to_string(), n_name.typ.name),
1691 )
1693 self.match("S_BRA")
1694 t_bracket = self.ct
1695 n_index = self.parse_expression(scope)
1696 self.match("S_KET")
1697 a_binary = ast.Binary_Operator.INDEX
1698 t_bracket.ast_link = a_binary
1699 self.ct.ast_link = a_binary
1701 n_name = ast.Binary_Expression(
1702 mh=self.mh,
1703 location=t_bracket.location,
1704 typ=n_name.typ.element_type,
1705 operator=a_binary,
1706 n_lhs=n_name,
1707 n_rhs=n_index,
1708 )
1710 return n_name
1712 def parse_check_block(self):
1713 # lobster-trace: LRM.Check_Block
1714 t_severity = None
1715 self.match_kw("checks")
1716 t_checks = self.ct
1717 self.match("IDENTIFIER")
1718 # lobster-trace: LRM.Applicable_Types
1719 # lobster-trace: LRM.Applicable_Components
1720 n_ctype = self.cu.package.symbols.lookup(self.mh, self.ct, ast.Composite_Type)
1721 n_check_block = ast.Check_Block(location=self.ct.location, n_typ=n_ctype)
1722 n_check_block.set_ast_link(t_checks)
1723 n_ctype.set_ast_link(self.ct)
1724 scope = ast.Scope()
1725 scope.push(self.stab)
1726 scope.push(self.cu.package.symbols)
1727 scope.push(n_ctype.components)
1728 self.match("C_BRA")
1729 n_check_block.set_ast_link(self.ct)
1730 while not self.peek("C_KET"):
1731 c_expr = self.parse_expression(scope)
1732 if not isinstance(c_expr.typ, ast.Builtin_Boolean): 1732 ↛ 1733line 1732 didn't jump to line 1733 because the condition on line 1732 was never true
1733 self.mh.error(c_expr.location, "check expression must be Boolean")
1735 self.match("COMMA")
1736 t_first_comma = self.ct
1737 if self.peek("KEYWORD"):
1738 self.match("KEYWORD")
1739 t_severity = self.ct
1740 if self.ct.value not in ("warning", "error", "fatal"): 1740 ↛ 1741line 1740 didn't jump to line 1741 because the condition on line 1740 was never true
1741 self.mh.error(self.ct.location, "expected warning|error|fatal")
1742 c_sev = self.ct.value
1743 else:
1744 c_sev = "error"
1746 self.match("STRING")
1747 if "\n" in self.ct.value:
1748 # lobster-trace: LRM.No_Newlines_In_Message
1749 self.mh.error(
1750 self.ct.location,
1751 "error message must not contain a newline",
1752 fatal=False,
1753 )
1754 t_msg = self.ct
1756 has_extrainfo = False
1757 has_anchor = False
1758 if self.peek("COMMA"):
1759 self.match("COMMA")
1760 t_second_comma = self.ct
1761 if self.peek("IDENTIFIER"):
1762 has_anchor = True
1763 elif self.peek("STRING"): 1763 ↛ 1766line 1763 didn't jump to line 1766 because the condition on line 1763 was always true
1764 has_extrainfo = True
1765 else:
1766 self.mh.error(
1767 self.nt.location,
1768 "expected either a details string or"
1769 " identifier to anchor the check message",
1770 )
1772 if has_extrainfo:
1773 self.match("STRING")
1774 t_extrainfo = self.ct
1775 c_extrainfo = self.ct.value
1777 if self.peek("COMMA"): 1777 ↛ 1778line 1777 didn't jump to line 1778 because the condition on line 1777 was never true
1778 self.match("COMMA")
1779 t_third_comma = self.ct
1780 has_anchor = True
1782 else:
1783 c_extrainfo = None
1785 if has_anchor:
1786 self.match("IDENTIFIER")
1787 t_anchor = self.ct
1788 c_anchor = n_ctype.components.lookup(
1789 self.mh, self.ct, ast.Composite_Component
1790 )
1791 else:
1792 c_anchor = None
1794 n_check = ast.Check(
1795 n_type=n_ctype,
1796 n_expr=c_expr,
1797 n_anchor=c_anchor,
1798 severity=c_sev,
1799 t_message=t_msg,
1800 extrainfo=c_extrainfo,
1801 )
1803 # pylint: disable=possibly-used-before-assignment
1804 # pylint: disable=used-before-assignment
1806 n_check.set_ast_link(t_first_comma)
1807 if t_severity:
1808 n_check.set_ast_link(t_severity)
1809 n_check.set_ast_link(t_msg)
1810 if c_extrainfo or c_anchor:
1811 n_check.set_ast_link(t_second_comma)
1812 if c_extrainfo:
1813 n_check.set_ast_link(t_extrainfo)
1814 if c_anchor:
1815 c_anchor.set_ast_link(t_anchor)
1816 if c_anchor and c_extrainfo: 1816 ↛ 1817line 1816 didn't jump to line 1817 because the condition on line 1816 was never true
1817 n_check.set_ast_link(t_third_comma)
1819 n_ctype.add_check(n_check)
1820 n_check_block.add_check(n_check)
1822 assert scope.size() == 3
1824 self.match("C_KET")
1825 n_check_block.set_ast_link(self.ct)
1827 return n_check_block
1829 def parse_section_declaration(self):
1830 # lobster-trace: LRM.Section_Declaration
1831 self.match_kw("section")
1832 t_section = self.ct
1833 self.match("STRING")
1834 sec = ast.Section(
1835 name=self.ct.value,
1836 location=self.ct.location,
1837 parent=self.section[-1] if self.section else None,
1838 )
1839 sec.set_ast_link(self.ct)
1840 sec.set_ast_link(t_section)
1841 self.section.append(sec)
1842 self.match("C_BRA")
1843 sec.set_ast_link(self.ct)
1844 while not self.peek("C_KET"):
1845 self.parse_trlc_entry()
1846 self.match("C_KET")
1847 sec.set_ast_link(self.ct)
1848 self.section.pop()
1850 def parse_boolean(self):
1851 # lobster-trace: LRM.Boolean_Values
1852 self.match("KEYWORD")
1853 if self.ct.value in ("true", "false"): 1853 ↛ 1856line 1853 didn't jump to line 1856 because the condition on line 1853 was always true
1854 return ast.Boolean_Literal(self.ct, self.builtin_bool)
1855 else:
1856 self.mh.error(self.ct.location, "expected boolean literal (true or false)")
1858 def parse_value(self, typ):
1859 # lobster-trace: LRM.Tuple_Syntax_Correct_Form
1860 assert isinstance(typ, ast.Type)
1862 if isinstance(typ, ast.Builtin_Numeric_Type):
1863 # lobster-trace: LRM.Integer_Values
1864 # lobster-trace: LRM.Decimal_Values
1865 if self.peek("OPERATOR") and self.nt.value in Parser.ADDING_OPERATOR:
1866 self.match("OPERATOR")
1867 t_op = self.ct
1868 e_op = (
1869 ast.Unary_Operator.PLUS
1870 if t_op.value == "+"
1871 else ast.Unary_Operator.MINUS
1872 )
1873 t_op.ast_link = e_op
1874 else:
1875 t_op = None
1877 if isinstance(typ, ast.Builtin_Decimal):
1878 self.match("DECIMAL")
1879 rv = ast.Decimal_Literal(self.ct, self.builtin_decimal)
1880 rv.set_ast_link(self.ct)
1881 elif isinstance(typ, ast.Builtin_Integer):
1882 self.match("INTEGER")
1883 rv = ast.Integer_Literal(self.ct, self.builtin_int)
1884 rv.set_ast_link(self.ct)
1885 else:
1886 assert False
1888 if t_op:
1889 rv = ast.Unary_Expression(
1890 mh=self.mh,
1891 location=t_op.location,
1892 typ=rv.typ,
1893 operator=e_op,
1894 n_operand=rv,
1895 )
1897 return rv
1899 elif isinstance(typ, ast.Builtin_Markup_String):
1900 # lobster-trace: LRM.Markup_String_Values
1901 return self.parse_markup_string()
1903 elif isinstance(typ, ast.Builtin_String):
1904 # lobster-trace: LRM.String_Values
1905 self.match("STRING")
1906 rv = ast.String_Literal(self.ct, self.builtin_str)
1907 rv.set_ast_link(self.ct)
1908 return rv
1910 elif isinstance(typ, ast.Builtin_Boolean):
1911 rv = self.parse_boolean()
1912 rv.set_ast_link(self.ct)
1913 return rv
1915 elif isinstance(typ, ast.Array_Type):
1916 self.match("S_BRA")
1917 rv = ast.Array_Aggregate(self.ct.location, typ)
1918 rv.set_ast_link(self.ct)
1919 while not self.peek("S_KET"):
1920 array_elem = self.parse_value(typ.element_type)
1921 rv.append(array_elem)
1922 if self.peek("COMMA"):
1923 self.match("COMMA")
1924 rv.set_ast_link(self.ct)
1925 elif self.peek("S_KET") or self.nt is None: 1925 ↛ 1928line 1925 didn't jump to line 1928 because the condition on line 1925 was always true
1926 break
1927 else:
1928 self.mh.error(
1929 self.ct.location,
1930 "comma separating array elements is missing",
1931 fatal=False,
1932 )
1934 self.match("S_KET")
1935 rv.set_ast_link(self.ct)
1937 if len(rv.value) < typ.lower_bound:
1938 self.mh.error(
1939 self.ct.location,
1940 "this array requires at least %u elements "
1941 "(only %u provided)" % (typ.lower_bound, len(rv.value)),
1942 fatal=False,
1943 )
1944 if typ.upper_bound and len(rv.value) > typ.upper_bound:
1945 self.mh.error(
1946 rv.value[typ.upper_bound].location,
1947 "this array requires at most %u elements "
1948 "(%u provided)" % (typ.upper_bound, len(rv.value)),
1949 fatal=False,
1950 )
1952 return rv
1954 elif isinstance(typ, ast.Enumeration_Type):
1955 enum = self.parse_qualified_name(self.default_scope, ast.Enumeration_Type)
1956 enum.set_ast_link(self.ct)
1957 if enum != typ:
1958 self.mh.error(self.ct.location, "expected %s" % typ.name)
1959 self.match("DOT")
1960 enum.set_ast_link(self.ct)
1961 self.match("IDENTIFIER")
1962 lit = enum.literals.lookup(self.mh, self.ct, ast.Enumeration_Literal_Spec)
1963 return ast.Enumeration_Literal(self.ct.location, lit)
1965 elif isinstance(typ, (ast.Record_Type, ast.Union_Type)):
1966 self.match("IDENTIFIER")
1967 t_name = self.ct
1968 if self.peek("DOT"):
1969 # Parse a (potentially nested) package prefix followed by
1970 # the object name via a greedy sub-package descent.
1971 the_pkg = self.stab.lookup(self.mh, t_name, ast.Package)
1972 the_pkg.set_ast_link(t_name)
1973 the_pkg, t_name = self.descend_sub_packages(the_pkg, t_name)
1974 else:
1975 the_pkg = self.cu.package
1977 rv = ast.Record_Reference(
1978 location=t_name.location, name=t_name.value, typ=typ, package=the_pkg
1979 )
1980 rv.set_ast_link(t_name)
1982 # We can do an early lookup if the target is known
1983 if the_pkg.symbols.contains(t_name.value):
1984 rv.resolve_references(self.mh)
1986 return rv
1988 elif isinstance(typ, ast.Tuple_Type) and typ.has_separators():
1989 # lobster-trace: LRM.Tuple_Separator_Form
1990 rv = ast.Tuple_Aggregate(self.nt.location, typ)
1992 next_is_optional = False
1993 for n_item in typ.iter_sequence():
1994 if isinstance(n_item, ast.Composite_Component):
1995 if next_is_optional and n_item.optional:
1996 break
1997 value = self.parse_value(n_item.n_typ)
1998 rv.assign(n_item.name, value)
2000 elif n_item.token.kind in ("AT", "COLON", "SEMICOLON"):
2001 if self.peek(n_item.token.kind):
2002 self.match(n_item.token.kind)
2003 n_item.set_ast_link(self.ct)
2004 else:
2005 next_is_optional = True
2007 elif n_item.token.kind == "IDENTIFIER":
2008 if self.peek("IDENTIFIER") and self.nt.value == n_item.token.value:
2009 self.match("IDENTIFIER")
2010 n_item.set_ast_link(self.ct)
2011 else:
2012 next_is_optional = True
2014 else:
2015 assert False
2017 return rv
2019 elif isinstance(typ, ast.Tuple_Type) and not typ.has_separators():
2020 # lobster-trace: LRM.Tuple_Generic_Form
2021 self.match("BRA")
2022 rv = ast.Tuple_Aggregate(self.ct.location, typ)
2023 rv.set_ast_link(self.ct)
2025 first = True
2026 for n_field in typ.iter_sequence():
2027 if first:
2028 first = False
2029 else:
2030 self.match("COMMA")
2031 rv.set_ast_link(self.ct)
2032 rv.assign(n_field.name, self.parse_value(n_field.n_typ))
2034 self.match("KET")
2035 rv.set_ast_link(self.ct)
2036 return rv
2038 else:
2039 self.mh.ice_loc(
2040 self.ct.location,
2041 "logic error: unexpected type %s" % typ.__class__.__name__,
2042 )
2044 def parse_markup_string(self):
2045 # lobster-trace: LRM.Markup_String_Values
2046 self.match("STRING")
2047 rv = ast.String_Literal(self.ct, self.builtin_mstr)
2048 mpar = Markup_Parser(self, rv)
2049 mpar.parse_all_references()
2050 return rv
2052 def parse_record_object_declaration(self):
2053 # lobster-trace: LRM.Section_Declaration
2054 # lobster-trace: LRM.Record_Object_Declaration
2055 # lobster-trace: LRM.Valid_Record_Types
2056 # lobster-trace: LRM.Valid_Components
2057 # lobster-trace: LRM.Valid_Enumeration_Literals
2058 # lobster-trace: LRM.Mandatory_Components
2059 # lobster-trace: LRM.Evaluation_Of_Checks
2060 # lobster-trace: LRM.Single_Value_Assignment
2062 r_typ = self.parse_qualified_name(self.default_scope, ast.Record_Type)
2063 r_typ.set_ast_link(self.ct)
2064 # lobster-trace: LRM.Abstract_Types
2065 if r_typ.is_abstract:
2066 self.mh.error(
2067 self.ct.location,
2068 "cannot declare object of abstract record type %s" % r_typ.name,
2069 )
2071 self.match("IDENTIFIER")
2072 obj = ast.Record_Object(
2073 name=self.ct.value,
2074 location=self.ct.location,
2075 n_typ=r_typ,
2076 section=self.section.copy() if self.section else None,
2077 n_package=self.cu.package,
2078 )
2079 self.cu.package.symbols.register(self.mh, obj)
2080 obj.set_ast_link(self.ct)
2082 self.match("C_BRA")
2083 obj.set_ast_link(self.ct)
2084 while not self.peek("C_KET"):
2085 self.match("IDENTIFIER")
2086 comp = r_typ.components.lookup(self.mh, self.ct, ast.Composite_Component)
2087 if obj.is_component_implicit_null(comp):
2088 self.mh.error(
2089 self.ct.location,
2090 "component '%s' already assigned at line %i"
2091 % (comp.name, obj.field[comp.name].location.line_no),
2092 )
2093 comp.set_ast_link(self.ct)
2094 if r_typ.is_frozen(comp):
2095 self.mh.error(
2096 self.ct.location, "cannot overwrite frozen component %s" % comp.name
2097 )
2098 self.match("ASSIGN")
2099 comp.set_ast_link(self.ct)
2100 value = self.parse_value(comp.n_typ)
2101 if not self.ct.ast_link:
2102 value.set_ast_link(self.ct)
2103 obj.assign(comp, value)
2105 # Check that each non-optional component has been specified
2106 for comp in r_typ.all_components():
2107 if isinstance(obj.field[comp.name], ast.Implicit_Null):
2108 if r_typ.is_frozen(comp):
2109 obj.assign(comp, r_typ.get_freezing_expression(comp))
2110 elif not comp.optional:
2111 self.mh.error(
2112 obj.location,
2113 "required component %s (see %s) is not defined"
2114 % (comp.name, self.mh.cross_file_reference(comp.location)),
2115 )
2117 self.match("C_KET")
2118 obj.set_ast_link(self.ct)
2120 return obj
2122 def parse_trlc_entry(self):
2123 # lobster-trace: LRM.TRLC_File
2124 if self.peek_kw("section"):
2125 self.parse_section_declaration()
2126 else:
2127 self.cu.add_item(self.parse_record_object_declaration())
2129 def parse_preamble(self, kind):
2130 assert kind in ("rsl", "trlc")
2131 # lobster-trace: LRM.Layout
2132 # lobster-trace: LRM.Preamble
2134 self.file_kind = kind
2136 # First, parse package indication, declaring the package if
2137 # needed
2138 self.match_kw(self.PACKAGE_KEYWORD)
2139 t_pkg = self.ct
2140 pkg_name, pkg_location, pkg_tokens = self.parse_dotted_name()
2142 if kind == "rsl":
2143 declare_package = True
2144 else:
2145 # lobster-trace: LRM.Late_Package_Declarations
2146 declare_package = not self.stab.contains(pkg_name)
2148 if declare_package:
2149 # lobster-trace: LRM.Package_Declaration
2150 pkg = ast.Package(
2151 name=pkg_name,
2152 location=pkg_location,
2153 builtin_stab=self.stab,
2154 declared_late=kind == "trlc",
2155 )
2156 self.stab.register(self.mh, pkg)
2157 else:
2158 pkg = self.stab.lookup_direct(self.mh, pkg_name, pkg_location, ast.Package)
2160 pkg.set_ast_link(t_pkg)
2161 for t in pkg_tokens:
2162 pkg.set_ast_link(t)
2164 # lobster-trace: LRM.Current_Package
2165 self.cu.set_package(pkg)
2167 self.default_scope.push(self.cu.package.symbols)
2169 # Second, parse import list (but don't resolve names yet)
2170 # lobster-trace: LRM.Import_Visibility
2171 if kind != "check": 2171 ↛ exitline 2171 didn't return from function 'parse_preamble' because the condition on line 2171 was always true
2172 while self.peek_kw("import"):
2173 self.match_kw("import")
2174 t_import_kw = self.ct
2175 imp_name, imp_location, imp_tokens, imp_wildcard = (
2176 self.parse_import_name()
2177 )
2178 pkg.set_ast_link(t_import_kw)
2179 self.cu.add_import(
2180 self.mh, imp_name, imp_location, imp_wildcard, imp_tokens
2181 )
2183 def parse_rsl_file(self):
2184 # lobster-trace: LRM.RSL_File
2185 assert self.cu.package is not None
2187 ok = True
2188 while not self.peek_eof():
2189 try:
2190 if self.peek_kw("checks"):
2191 self.cu.add_item(self.parse_check_block())
2192 else:
2193 self.cu.add_item(self.parse_type_declaration())
2194 except TRLC_Error as err:
2195 if not self.error_recovery or err.kind == "lex error":
2196 raise
2198 ok = False
2200 # Recovery strategy is to scan until we get the next
2201 # relevant keyword
2202 self.skip_until_newline()
2203 while not self.peek_eof():
2204 if (
2205 self.peek_kw("checks")
2206 or self.peek_kw("type")
2207 or self.peek_kw("abstract")
2208 or self.peek_kw("final")
2209 or self.peek_kw("tuple")
2210 or self.peek_kw("enum")
2211 ):
2212 break
2213 self.advance()
2214 self.skip_until_newline()
2216 self.match_eof()
2218 for tok in self.lexer.tokens:
2219 if tok.kind == "COMMENT":
2220 self.cu.package.set_ast_link(tok)
2222 return ok
2224 def parse_trlc_file(self):
2225 # lobster-trace: LRM.TRLC_File
2226 assert self.cu.package is not None
2228 ok = True
2230 while self.peek_kw("section") or self.peek("IDENTIFIER"):
2231 try:
2232 self.parse_trlc_entry()
2233 except TRLC_Error as err:
2234 if not self.error_recovery or err.kind == "lex error":
2235 raise
2237 ok = False
2239 # Recovery strategy is to keep going until we find an
2240 # identifier that is a package or type, or section, or
2241 # EOF
2242 self.skip_until_newline()
2243 while not self.peek_eof():
2244 if self.peek_kw("section"): 2244 ↛ 2245line 2244 didn't jump to line 2245 because the condition on line 2244 was never true
2245 break
2246 elif not self.peek("IDENTIFIER"):
2247 pass
2248 elif self.stab.contains(self.nt.value): 2248 ↛ 2249line 2248 didn't jump to line 2249 because the condition on line 2248 was never true
2249 n_sym = self.stab.lookup_assuming(self.mh, self.nt.value)
2250 if isinstance(n_sym, ast.Package):
2251 break
2252 elif self.cu.package.symbols.contains(self.nt.value):
2253 n_sym = self.cu.package.symbols.lookup_assuming(
2254 self.mh, self.nt.value
2255 )
2256 if isinstance(n_sym, ast.Record_Type):
2257 break
2258 self.advance()
2259 self.skip_until_newline()
2261 self.match_eof()
2263 for tok in self.lexer.tokens:
2264 if tok.kind == "COMMENT":
2265 self.cu.package.set_ast_link(tok)
2267 return ok