Coverage for trlc/parser.py: 96%
1123 statements
« prev ^ index » next coverage.py v7.10.7, created at 2026-09-04 11:46 +0000
« prev ^ index » next coverage.py v7.10.7, created at 2026-09-04 11:46 +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 def __init__(
294 self,
295 mh,
296 stab,
297 file_name,
298 lint_mode,
299 error_recovery,
300 primary_file=True,
301 lexer=None,
302 ):
303 assert isinstance(mh, Message_Handler)
304 assert isinstance(stab, ast.Symbol_Table)
305 assert isinstance(file_name, str)
306 assert isinstance(lint_mode, bool)
307 assert isinstance(error_recovery, bool)
308 assert isinstance(primary_file, bool)
309 assert isinstance(lexer, TRLC_Lexer) or lexer is None
310 if lexer: 310 ↛ 319line 310 didn't jump to line 319 because the condition on line 310 was always true
311 super().__init__(
312 mh,
313 lexer,
314 eoc_name="end-of-file",
315 token_map=Token.KIND,
316 keywords=TRLC_Lexer.KEYWORDS,
317 )
318 else:
319 super().__init__(
320 mh,
321 TRLC_Lexer(mh, file_name),
322 eoc_name="end-of-file",
323 token_map=Token.KIND,
324 keywords=TRLC_Lexer.KEYWORDS,
325 )
327 self.lint_mode = lint_mode
328 self.error_recovery = error_recovery
330 self.stab = stab
331 self.cu = ast.Compilation_Unit(file_name)
333 self.primary = primary_file
334 self.secondary = False
335 # Controls if the file is actually fully parsed: primary means
336 # it was selected on the command-line and secondary means it
337 # was selected by dependency analysis.
339 self.builtin_bool = stab.lookup_assuming(self.mh, "Boolean")
340 self.builtin_int = stab.lookup_assuming(self.mh, "Integer")
341 self.builtin_decimal = stab.lookup_assuming(self.mh, "Decimal")
342 self.builtin_str = stab.lookup_assuming(self.mh, "String")
343 self.builtin_mstr = stab.lookup_assuming(self.mh, "Markup_String")
345 self.section = []
346 self.default_scope = ast.Scope()
347 self.default_scope.push(self.stab)
349 def parse_described_name(self):
350 # lobster-trace: LRM.Described_Names
351 # lobster-trace: LRM.Described_Name_Description
352 self.match("IDENTIFIER")
353 name = self.ct
355 if self.peek("STRING"):
356 self.match("STRING")
357 t_descr = self.ct
358 return name, t_descr.value, t_descr
359 else:
360 return name, None, None
362 def parse_qualified_name(self, scope, required_subclass=None, match_ident=True):
363 # lobster-trace: LRM.Qualified_Name
364 # lobster-trace: LRM.Valid_Qualifier
365 # lobster-trace: LRM.Valid_Name
366 assert isinstance(scope, ast.Scope)
367 assert required_subclass is None or isinstance(required_subclass, type)
368 assert isinstance(match_ident, bool)
370 if match_ident:
371 self.match("IDENTIFIER")
372 sym = scope.lookup(self.mh, self.ct)
373 sym.set_ast_link(self.ct)
375 if isinstance(sym, ast.Package):
376 if not self.cu.is_visible(sym):
377 self.mh.error(self.ct.location, "package must be imported before use")
378 self.match("DOT")
379 sym.set_ast_link(self.ct)
380 self.match("IDENTIFIER")
381 return sym.symbols.lookup(self.mh, self.ct, required_subclass)
382 else:
383 # Easiest way to generate the correct error message
384 return scope.lookup(self.mh, self.ct, required_subclass)
386 def parse_type_declaration(self):
387 # lobster-trace: LRM.Type_Declarations
388 if self.peek_kw("enum"):
389 n_item = self.parse_enum_declaration()
390 elif self.peek_kw("tuple"):
391 n_item = self.parse_tuple_declaration()
392 else:
393 n_item = self.parse_record_declaration()
394 assert isinstance(n_item, ast.Concrete_Type)
395 return n_item
397 def parse_enum_declaration(self):
398 # lobster-trace: LRM.Enumeration_Declaration
399 self.match_kw("enum")
400 t_enum = self.ct
401 name, description, t_description = self.parse_described_name()
403 enum = ast.Enumeration_Type(
404 name=name.value,
405 description=description,
406 location=name.location,
407 package=self.cu.package,
408 )
409 self.cu.package.symbols.register(self.mh, enum)
410 enum.set_ast_link(t_enum)
411 enum.set_ast_link(name)
412 if t_description:
413 enum.set_ast_link(t_description)
415 self.match("C_BRA")
416 enum.set_ast_link(self.ct)
417 empty = True
418 while not self.peek("C_KET"):
419 name, description, t_description = self.parse_described_name()
420 lit = ast.Enumeration_Literal_Spec(
421 name=name.value,
422 description=description,
423 location=name.location,
424 enum=enum,
425 )
426 lit.set_ast_link(name)
427 if t_description:
428 lit.set_ast_link(self.ct)
429 empty = False
430 enum.literals.register(self.mh, lit)
431 self.match("C_KET")
432 enum.set_ast_link(self.ct)
434 if empty:
435 # lobster-trace: LRM.No_Empty_Enumerations
436 self.mh.error(enum.location, "empty enumerations are not permitted")
438 return enum
440 def parse_tuple_field(
441 self, n_tuple, optional_allowed, optional_reason, optional_required
442 ):
443 assert isinstance(n_tuple, ast.Tuple_Type)
444 assert isinstance(optional_allowed, bool)
445 assert isinstance(optional_reason, str)
446 assert isinstance(optional_required, bool)
447 assert optional_allowed or not optional_required
449 field_name, field_description, t_descr = self.parse_described_name()
451 if optional_required or self.peek_kw("optional"):
452 self.match_kw("optional")
453 t_optional = self.ct
454 field_is_optional = True
455 if not optional_allowed:
456 self.mh.error(self.ct.location, optional_reason)
457 else:
458 field_is_optional = False
459 t_optional = None
461 # lobster-trace: LRM.Tuple_Field_Types
462 # S_BRA here means a union type '[T1, T2, ...]', not array bounds.
463 if self.peek("S_BRA"):
464 # lobster-trace: LRM.union_type
465 field_type = self.parse_union_type()
466 else:
467 field_type = self.parse_qualified_name(self.default_scope, ast.Type)
468 comp = ast.Composite_Component(
469 name=field_name.value,
470 description=field_description,
471 location=field_name.location,
472 member_of=n_tuple,
473 n_typ=field_type,
474 optional=field_is_optional,
475 )
476 comp.set_ast_link(field_name)
477 if t_descr:
478 comp.set_ast_link(t_descr)
479 if field_is_optional:
480 comp.set_ast_link(t_optional)
482 return comp
484 def parse_tuple_declaration(self):
485 # lobster-trace: LRM.Tuple_Declaration
486 self.match_kw("tuple")
487 t_tuple = self.ct
488 name, description, t_descr = self.parse_described_name()
490 n_tuple = ast.Tuple_Type(
491 name=name.value,
492 description=description,
493 location=name.location,
494 package=self.cu.package,
495 )
497 n_tuple.set_ast_link(t_tuple)
498 n_tuple.set_ast_link(name)
499 if t_descr:
500 n_tuple.set_ast_link(t_descr)
501 self.match("C_BRA")
502 n_tuple.set_ast_link(self.ct)
504 n_field = self.parse_tuple_field(
505 n_tuple,
506 optional_allowed=False,
507 optional_reason="first field may not be optional",
508 optional_required=False,
509 )
510 n_tuple.components.register(self.mh, n_field)
512 has_separators = False
513 optional_required = False
514 separator_allowed = True
516 while self.peek_kw("separator") or self.peek("IDENTIFIER"):
517 if has_separators or self.peek_kw("separator"):
518 has_separators = True
519 self.match_kw("separator")
520 t_sep = self.ct
521 if not separator_allowed:
522 # lobster-trace: LRM.Tuple_Separators_All_Or_None
523 self.mh.error(
524 self.ct.location, "either all fields must be separated, or none"
525 )
526 if ( 526 ↛ 540line 526 didn't jump to line 540 because the condition on line 526 was always true
527 self.peek("IDENTIFIER")
528 or self.peek("AT")
529 or self.peek("COLON")
530 or self.peek("SEMICOLON")
531 ):
532 self.advance()
533 sep = ast.Separator(self.ct)
534 sep.set_ast_link(t_sep)
535 sep.set_ast_link(self.ct)
536 n_tuple.add_separator(sep)
537 else:
538 separator_allowed = False
539 # lobster-trace: LRM.Tuple_Optional_Requires_Separators
540 n_field = self.parse_tuple_field(
541 n_tuple,
542 optional_allowed=has_separators,
543 optional_reason=("optional only permitted in tuples with separators"),
544 optional_required=optional_required,
545 )
546 n_tuple.components.register(self.mh, n_field)
547 # lobster-trace: LRM.Tuple_Optional_Fields
548 optional_required |= n_field.optional
550 self.match("C_KET")
551 n_tuple.set_ast_link(self.ct)
553 # Final check to ban tuples with separators containing other
554 # tuples.
555 if has_separators:
556 # lobster-trace: LRM.Restricted_Tuple_Nesting
557 for n_field in n_tuple.components.values():
558 if (
559 isinstance(n_field.n_typ, ast.Tuple_Type)
560 and n_field.n_typ.has_separators()
561 ):
562 self.mh.error(
563 n_field.location,
564 "tuple type %s, which contains separators,"
565 " may not contain another tuple with separators" % n_tuple.name,
566 )
568 # Late registration to avoid recursion in tuples
569 # lobster-trace: LRM.Tuple_Field_Types
570 self.cu.package.symbols.register(self.mh, n_tuple)
572 return n_tuple
574 def parse_union_type(self):
575 """Parse a union type declaration: '[' Type1 ',' Type2 ... ']'
577 The leading S_BRA must be the next token when called.
578 Returns an ast.Union_Type node.
579 """
580 # lobster-trace: LRM.union_type
581 # lobster-trace: LRM.Union_Type_No_Duplicates
582 # lobster-trace: LRM.Union_Type_Record_Types_Only
583 self.match("S_BRA")
584 t_s_bra = self.ct
586 union_type_entries = [] # list of (Record_Type, Location)
587 first_type = self.parse_qualified_name(self.default_scope, ast.Record_Type)
588 first_type.set_ast_link(self.ct)
589 union_type_entries.append((first_type, self.ct.location))
591 while self.peek("COMMA"):
592 self.match("COMMA")
593 next_type = self.parse_qualified_name(self.default_scope, ast.Record_Type)
594 next_type.set_ast_link(self.ct)
595 union_type_entries.append((next_type, self.ct.location))
597 self.match("S_KET")
598 t_s_ket = self.ct
600 seen = {}
601 for t, loc in union_type_entries:
602 fqn = t.fully_qualified_name()
603 if fqn in seen:
604 self.mh.error(loc, "duplicate type %s in union" % t.name, fatal=False)
605 else:
606 seen[fqn] = loc
608 union_types = [t for t, _ in union_type_entries]
609 c_typ = ast.Union_Type(location=t_s_bra.location, types=union_types)
610 c_typ.set_ast_link(t_s_bra)
611 c_typ.set_ast_link(t_s_ket)
612 return c_typ
614 def parse_record_component(self, n_record):
615 assert isinstance(n_record, ast.Record_Type)
617 c_name, c_descr, t_descr = self.parse_described_name()
618 t_optional = None
619 c_optional = False
620 if self.peek_kw("optional"):
621 self.match_kw("optional")
622 t_optional = self.ct
623 c_optional = True
625 # S_BRA here means a union type '[T1, T2, ...]', not array bounds.
626 # Array bounds '[INTEGER..INTEGER]' are checked in the next block.
627 if self.peek("S_BRA"):
628 c_typ = self.parse_union_type()
629 else:
630 c_typ = self.parse_qualified_name(self.default_scope, ast.Type)
631 c_typ.set_ast_link(self.ct)
633 if self.peek("S_BRA"):
634 self.match("S_BRA")
635 t_s_bra = self.ct
636 self.match("INTEGER")
637 t_lo = self.ct
638 a_lo = self.ct.value
639 loc_lo = self.ct.location
640 self.match("RANGE")
641 t_range = self.ct
642 a_loc = self.ct.location
643 a_hi = None
644 if self.peek("INTEGER"):
645 self.match("INTEGER")
646 a_hi = self.ct.value
647 elif self.peek("OPERATOR") and self.nt.value == "*": 647 ↛ 650line 647 didn't jump to line 650 because the condition on line 647 was always true
648 self.match("OPERATOR")
649 else:
650 self.mh.error(self.nt.location, "expected INTEGER or * for upper bound")
651 t_hi = self.ct
652 loc_hi = self.ct.location
653 self.match("S_KET")
654 t_s_ket = self.ct
655 c_typ = ast.Array_Type(
656 location=a_loc,
657 element_type=c_typ,
658 lower_bound=a_lo,
659 upper_bound=a_hi,
660 loc_lower=loc_lo,
661 loc_upper=loc_hi,
662 )
663 c_typ.set_ast_link(t_s_bra)
664 c_typ.set_ast_link(t_lo)
665 c_typ.set_ast_link(t_range)
666 c_typ.set_ast_link(t_hi)
667 c_typ.set_ast_link(t_s_ket)
669 c_comp = ast.Composite_Component(
670 name=c_name.value,
671 description=c_descr,
672 location=c_name.location,
673 member_of=n_record,
674 n_typ=c_typ,
675 optional=c_optional,
676 )
677 c_comp.set_ast_link(c_name)
678 if t_descr:
679 c_comp.set_ast_link(t_descr)
680 if c_optional:
681 c_comp.set_ast_link(t_optional)
683 return c_comp
685 def parse_record_declaration(self):
686 t_abstract = None
687 t_final = None
688 is_abstract = False
689 is_final = False
690 if self.peek_kw("abstract"):
691 self.match_kw("abstract")
692 t_abstract = self.ct
693 is_abstract = True
694 elif self.peek_kw("final"):
695 self.match_kw("final")
696 t_final = self.ct
697 is_final = True
699 self.match_kw("type")
700 t_type = self.ct
701 name, description, t_description = self.parse_described_name()
703 if self.peek_kw("extends"):
704 self.match_kw("extends")
705 t_extends = self.ct
706 root_record = self.parse_qualified_name(self.default_scope, ast.Record_Type)
707 root_record.set_ast_link(t_extends)
708 root_record.set_ast_link(self.ct)
709 else:
710 root_record = None
712 if self.lint_mode and root_record and root_record.is_final and not is_final:
713 self.mh.check(
714 name.location,
715 "consider clarifying that this record is final",
716 "clarify_final",
717 (
718 "Parent record %s is final, making this record\n"
719 "also final. Marking it explicitly as final\n"
720 "clarifies this to casual readers."
721 % root_record.fully_qualified_name()
722 ),
723 )
725 record = ast.Record_Type(
726 name=name.value,
727 description=description,
728 location=name.location,
729 package=self.cu.package,
730 n_parent=root_record,
731 is_abstract=is_abstract,
732 )
733 self.cu.package.symbols.register(self.mh, record)
734 if is_abstract:
735 record.set_ast_link(t_abstract)
736 if is_final:
737 record.set_ast_link(t_final)
738 record.set_ast_link(t_type)
739 record.set_ast_link(name)
740 if t_description:
741 record.set_ast_link(t_description)
743 self.match("C_BRA")
744 record.set_ast_link(self.ct)
745 while not self.peek("C_KET"):
746 if self.peek_kw("freeze"):
747 self.match_kw("freeze")
748 t_freeze = self.ct
749 self.match("IDENTIFIER")
750 n_comp = record.components.lookup(
751 self.mh, self.ct, ast.Composite_Component
752 )
753 if record.is_frozen(n_comp):
754 n_value = record.get_freezing_expression(n_comp)
755 self.mh.error(
756 self.ct.location,
757 "duplicate freezing of %s, previously frozen at %s"
758 % (n_comp.name, self.mh.cross_file_reference(n_value.location)),
759 )
760 n_comp.set_ast_link(t_freeze)
761 n_comp.set_ast_link(self.ct)
762 self.match("ASSIGN")
763 n_comp.set_ast_link(self.ct)
764 n_value = self.parse_value(n_comp.n_typ)
765 n_value.set_ast_link(self.ct)
767 record.frozen[n_comp.name] = n_value
769 else:
770 n_comp = self.parse_record_component(record)
771 if record.is_final:
772 self.mh.error(
773 n_comp.location,
774 "cannot declare new components in final record type",
775 )
776 else:
777 record.components.register(self.mh, n_comp)
779 self.match("C_KET")
780 record.set_ast_link(self.ct)
782 # Finally mark record final if applicable
783 if is_final:
784 record.is_final = True
786 return record
788 def parse_expression(self, scope):
789 # lobster-trace: LRM.Expression
790 assert isinstance(scope, ast.Scope)
792 n_lhs = self.parse_relation(scope)
794 if self.peek_kw("and"):
795 while self.peek_kw("and"):
796 self.match_kw("and")
797 t_op = self.ct
798 a_op = ast.Binary_Operator.LOGICAL_AND
799 t_op.ast_link = a_op
800 n_rhs = self.parse_relation(scope)
801 n_lhs = ast.Binary_Expression(
802 mh=self.mh,
803 location=t_op.location,
804 typ=self.builtin_bool,
805 operator=a_op,
806 n_lhs=n_lhs,
807 n_rhs=n_rhs,
808 )
810 elif self.peek_kw("or"):
811 while self.peek_kw("or"):
812 self.match_kw("or")
813 t_op = self.ct
814 a_op = ast.Binary_Operator.LOGICAL_OR
815 t_op.ast_link = a_op
816 n_rhs = self.parse_relation(scope)
817 n_lhs = ast.Binary_Expression(
818 mh=self.mh,
819 location=t_op.location,
820 typ=self.builtin_bool,
821 operator=a_op,
822 n_lhs=n_lhs,
823 n_rhs=n_rhs,
824 )
826 elif self.peek_kw("xor"):
827 self.match_kw("xor")
828 t_op = self.ct
829 a_op = ast.Binary_Operator.LOGICAL_XOR
830 t_op.ast_link = a_op
831 n_rhs = self.parse_relation(scope)
832 n_lhs = ast.Binary_Expression(
833 mh=self.mh,
834 location=t_op.location,
835 typ=self.builtin_bool,
836 operator=a_op,
837 n_lhs=n_lhs,
838 n_rhs=n_rhs,
839 )
841 elif self.peek_kw("implies"):
842 self.match_kw("implies")
843 t_op = self.ct
844 a_op = ast.Binary_Operator.LOGICAL_IMPLIES
845 t_op.ast_link = a_op
846 n_rhs = self.parse_relation(scope)
847 n_lhs = ast.Binary_Expression(
848 mh=self.mh,
849 location=t_op.location,
850 typ=self.builtin_bool,
851 operator=a_op,
852 n_lhs=n_lhs,
853 n_rhs=n_rhs,
854 )
856 return n_lhs
858 def parse_relation(self, scope):
859 # lobster-trace: LRM.Relation
860 # lobster-trace: LRM.Operators
861 assert isinstance(scope, ast.Scope)
862 relop_mapping = {
863 "==": ast.Binary_Operator.COMP_EQ,
864 "!=": ast.Binary_Operator.COMP_NEQ,
865 "<": ast.Binary_Operator.COMP_LT,
866 "<=": ast.Binary_Operator.COMP_LEQ,
867 ">": ast.Binary_Operator.COMP_GT,
868 ">=": ast.Binary_Operator.COMP_GEQ,
869 }
870 assert set(relop_mapping) == set(Parser.COMPARISON_OPERATOR)
872 n_lhs = self.parse_simple_expression(scope)
874 if self.peek("OPERATOR") and self.nt.value in Parser.COMPARISON_OPERATOR:
875 self.match("OPERATOR")
876 t_op = self.ct
877 a_op = relop_mapping[t_op.value]
878 t_op.ast_link = a_op
879 n_rhs = self.parse_simple_expression(scope)
880 return ast.Binary_Expression(
881 mh=self.mh,
882 location=t_op.location,
883 typ=self.builtin_bool,
884 operator=a_op,
885 n_lhs=n_lhs,
886 n_rhs=n_rhs,
887 )
889 elif self.peek_kw("not") or self.peek_kw("in"):
890 if self.peek_kw("not"):
891 self.match_kw("not")
892 t_not = self.ct
893 else:
894 t_not = None
896 self.match_kw("in")
897 t_in = self.ct
899 n_a = self.parse_simple_expression(scope)
900 t_n_a = self.ct
901 if self.peek("RANGE"):
902 self.match("RANGE")
903 t_range = self.ct
904 n_b = self.parse_simple_expression(scope)
905 n_b.set_ast_link(self.ct)
906 n_a.set_ast_link(t_n_a)
907 rv = ast.Range_Test(
908 mh=self.mh,
909 location=t_in.location,
910 typ=self.builtin_bool,
911 n_lhs=n_lhs,
912 n_lower=n_a,
913 n_upper=n_b,
914 )
915 rv.set_ast_link(t_range)
916 rv.set_ast_link(t_in)
918 elif isinstance(n_a.typ, ast.Builtin_String):
919 rv = ast.Binary_Expression(
920 mh=self.mh,
921 location=t_in.location,
922 typ=self.builtin_bool,
923 operator=ast.Binary_Operator.STRING_CONTAINS,
924 n_lhs=n_lhs,
925 n_rhs=n_a,
926 )
927 rv.set_ast_link(t_in)
929 elif isinstance(n_a.typ, ast.Array_Type): 929 ↛ 942line 929 didn't jump to line 942 because the condition on line 929 was always true
930 a_op = ast.Binary_Operator.ARRAY_CONTAINS
931 t_in.ast_link = a_op
932 rv = ast.Binary_Expression(
933 mh=self.mh,
934 location=t_in.location,
935 typ=self.builtin_bool,
936 operator=a_op,
937 n_lhs=n_lhs,
938 n_rhs=n_a,
939 )
941 else:
942 self.mh.error(
943 n_a.location,
944 "membership test only defined for Strings and Arrays,"
945 " not for %s" % n_a.typ.name,
946 )
948 if t_not is not None:
949 a_unary_op = ast.Unary_Operator.LOGICAL_NOT
950 t_not.ast_link = a_unary_op
951 rv = ast.Unary_Expression(
952 mh=self.mh,
953 location=t_not.location,
954 typ=self.builtin_bool,
955 operator=a_unary_op,
956 n_operand=rv,
957 )
959 return rv
961 else:
962 return n_lhs
964 def parse_simple_expression(self, scope):
965 # lobster-trace: LRM.Simple_Expression
966 # lobster-trace: LRM.Operators
967 # lobster-trace: LRM.Unary_Minus_Parsing
968 assert isinstance(scope, ast.Scope)
969 un_add_map = {"+": ast.Unary_Operator.PLUS, "-": ast.Unary_Operator.MINUS}
970 bin_add_map = {"+": ast.Binary_Operator.PLUS, "-": ast.Binary_Operator.MINUS}
971 assert set(un_add_map) == set(Parser.ADDING_OPERATOR)
972 assert set(bin_add_map) == set(Parser.ADDING_OPERATOR)
974 if self.peek("OPERATOR") and self.nt.value in Parser.ADDING_OPERATOR:
975 self.match("OPERATOR")
976 t_unary = self.ct
977 a_unary = un_add_map[t_unary.value]
978 t_unary.ast_link = a_unary
979 has_explicit_brackets = self.peek("BRA")
980 else:
981 t_unary = None
983 n_lhs = self.parse_term(scope)
984 if t_unary:
985 # pylint: disable=possibly-used-before-assignment
986 if (
987 self.lint_mode
988 and isinstance(n_lhs, ast.Binary_Expression)
989 and not has_explicit_brackets
990 ):
991 self.mh.check(
992 t_unary.location,
993 "expression means -(%s), place explicit "
994 "brackets to clarify intent" % n_lhs.to_string(),
995 "unary_minus_precedence",
996 )
998 n_lhs = ast.Unary_Expression(
999 mh=self.mh,
1000 location=t_unary.location,
1001 typ=n_lhs.typ,
1002 operator=a_unary,
1003 n_operand=n_lhs,
1004 )
1006 if isinstance(n_lhs.typ, ast.Builtin_String):
1007 rtyp = self.builtin_str
1008 else:
1009 rtyp = n_lhs.typ
1011 while self.peek("OPERATOR") and self.nt.value in Parser.ADDING_OPERATOR:
1012 self.match("OPERATOR")
1013 t_op = self.ct
1014 a_op = bin_add_map[t_op.value]
1015 t_op.ast_link = a_op
1016 n_rhs = self.parse_term(scope)
1017 n_lhs = ast.Binary_Expression(
1018 mh=self.mh,
1019 location=t_op.location,
1020 typ=rtyp,
1021 operator=a_op,
1022 n_lhs=n_lhs,
1023 n_rhs=n_rhs,
1024 )
1026 return n_lhs
1028 def parse_term(self, scope):
1029 # lobster-trace: LRM.Term
1030 # lobster-trace: LRM.Operators
1031 assert isinstance(scope, ast.Scope)
1032 mul_map = {
1033 "*": ast.Binary_Operator.TIMES,
1034 "/": ast.Binary_Operator.DIVIDE,
1035 "%": ast.Binary_Operator.REMAINDER,
1036 }
1037 assert set(mul_map) == set(Parser.MULTIPLYING_OPERATOR)
1039 n_lhs = self.parse_factor(scope)
1040 while self.peek("OPERATOR") and self.nt.value in Parser.MULTIPLYING_OPERATOR:
1041 self.match("OPERATOR")
1042 t_op = self.ct
1043 a_op = mul_map[t_op.value]
1044 t_op.ast_link = a_op
1045 n_rhs = self.parse_factor(scope)
1046 n_lhs = ast.Binary_Expression(
1047 mh=self.mh,
1048 location=t_op.location,
1049 typ=n_lhs.typ,
1050 operator=a_op,
1051 n_lhs=n_lhs,
1052 n_rhs=n_rhs,
1053 )
1055 return n_lhs
1057 def parse_factor(self, scope):
1058 # lobster-trace: LRM.Factor
1059 assert isinstance(scope, ast.Scope)
1061 if self.peek_kw("not"):
1062 self.match_kw("not")
1063 t_op = self.ct
1064 n_operand = self.parse_primary(scope)
1065 a_not = ast.Unary_Operator.LOGICAL_NOT
1066 t_op.ast_link = a_not
1067 return ast.Unary_Expression(
1068 mh=self.mh,
1069 location=t_op.location,
1070 typ=self.builtin_bool,
1071 operator=a_not,
1072 n_operand=n_operand,
1073 )
1075 elif self.peek_kw("abs"):
1076 self.match_kw("abs")
1077 t_op = self.ct
1078 n_operand = self.parse_primary(scope)
1079 a_abs = ast.Unary_Operator.ABSOLUTE_VALUE
1080 t_op.ast_link = a_abs
1081 return ast.Unary_Expression(
1082 mh=self.mh,
1083 location=t_op.location,
1084 typ=n_operand.typ,
1085 operator=a_abs,
1086 n_operand=n_operand,
1087 )
1089 else:
1090 n_lhs = self.parse_primary(scope)
1091 if self.peek("OPERATOR") and self.nt.value == "**":
1092 self.match("OPERATOR")
1093 t_op = self.ct
1094 n_rhs = self.parse_primary(scope)
1095 rhs_value = n_rhs.evaluate(self.mh, None, None)
1096 a_binary = ast.Binary_Operator.POWER
1097 t_op.ast_link = a_binary
1098 n_lhs = ast.Binary_Expression(
1099 mh=self.mh,
1100 location=t_op.location,
1101 typ=n_lhs.typ,
1102 operator=a_binary,
1103 n_lhs=n_lhs,
1104 n_rhs=n_rhs,
1105 )
1106 if rhs_value.value < 0: 1106 ↛ 1107line 1106 didn't jump to line 1107 because the condition on line 1106 was never true
1107 self.mh.error(n_rhs.location, "exponent must not be negative")
1108 return n_lhs
1110 def parse_primary(self, scope):
1111 # lobster-trace: LRM.Primary
1112 assert isinstance(scope, ast.Scope)
1114 if self.peek("INTEGER"):
1115 # lobster-trace: LRM.Integer_Values
1116 self.match("INTEGER")
1117 int_lit = ast.Integer_Literal(self.ct, self.builtin_int)
1118 int_lit.set_ast_link(self.ct)
1119 return int_lit
1121 elif self.peek("DECIMAL"):
1122 # lobster-trace: LRM.Decimal_Values
1123 self.match("DECIMAL")
1124 dec_lit = ast.Decimal_Literal(self.ct, self.builtin_decimal)
1125 dec_lit.set_ast_link(self.ct)
1126 return dec_lit
1128 elif self.peek("STRING"):
1129 # lobster-trace: LRM.String_Values
1130 self.match("STRING")
1131 string_lit = ast.String_Literal(self.ct, self.builtin_str)
1132 string_lit.set_ast_link(self.ct)
1133 return string_lit
1135 elif self.peek_kw("true") or self.peek_kw("false"):
1136 # lobster-trace: LRM.Boolean_Values
1137 self.match("KEYWORD")
1138 bool_lit = ast.Boolean_Literal(self.ct, self.builtin_bool)
1139 bool_lit.set_ast_link(self.ct)
1140 return bool_lit
1142 elif self.peek_kw("null"):
1143 self.match_kw("null")
1144 null_lit = ast.Null_Literal(self.ct)
1145 null_lit.set_ast_link(self.ct)
1146 return null_lit
1148 elif self.peek("BRA"):
1149 self.match("BRA")
1150 t_bra = self.ct
1151 if self.peek_kw("forall") or self.peek_kw("exists"):
1152 rv = self.parse_quantified_expression(scope)
1153 elif self.peek_kw("if"):
1154 rv = self.parse_conditional_expression(scope)
1155 else:
1156 rv = self.parse_expression(scope)
1157 rv.set_ast_link(t_bra)
1158 self.match("KET")
1159 rv.set_ast_link(self.ct)
1160 return rv
1162 else:
1163 return self.parse_name(scope)
1165 def parse_quantified_expression(self, scope):
1166 # lobster-trace: LRM.Quantified_Expression
1167 assert isinstance(scope, ast.Scope)
1169 if self.peek_kw("forall"):
1170 self.match_kw("forall")
1171 t_quantified = self.ct
1172 universal = True
1173 else:
1174 self.match_kw("exists")
1175 t_quantified = self.ct
1176 universal = False
1177 loc = self.ct.location
1178 self.match("IDENTIFIER")
1179 t_qv = self.ct
1180 if scope.contains(t_qv.value):
1181 # lobster-trace: LRM.Quantification_Naming_Scope
1182 pdef = scope.lookup(self.mh, t_qv)
1183 self.mh.error(
1184 t_qv.location,
1185 "shadows %s %s from %s"
1186 % (
1187 pdef.__class__.__name__,
1188 pdef.name,
1189 self.mh.cross_file_reference(pdef.location),
1190 ),
1191 )
1192 self.match_kw("in")
1193 t_in = self.ct
1194 self.match("IDENTIFIER")
1195 field = scope.lookup(self.mh, self.ct, ast.Composite_Component)
1196 n_source = ast.Name_Reference(self.ct.location, field)
1197 n_source.set_ast_link(self.ct)
1198 if not isinstance(field.n_typ, ast.Array_Type):
1199 # lobster-trace: LRM.Quantification_Object
1200 self.mh.error(self.ct.location, "you can only quantify over arrays")
1201 n_var = ast.Quantified_Variable(
1202 t_qv.value, t_qv.location, field.n_typ.element_type
1203 )
1204 n_var.set_ast_link(t_qv)
1205 self.match("ARROW")
1206 t_arrow = self.ct
1208 new_table = ast.Symbol_Table()
1209 new_table.register(self.mh, n_var)
1210 scope.push(new_table)
1211 n_expr = self.parse_expression(scope)
1212 scope.pop()
1214 quantified_expression = ast.Quantified_Expression(
1215 mh=self.mh,
1216 location=loc,
1217 typ=self.builtin_bool,
1218 universal=universal,
1219 n_variable=n_var,
1220 n_source=n_source,
1221 n_expr=n_expr,
1222 )
1224 quantified_expression.set_ast_link(t_quantified)
1225 quantified_expression.set_ast_link(t_in)
1226 quantified_expression.set_ast_link(t_arrow)
1228 return quantified_expression
1230 def parse_conditional_expression(self, scope):
1231 # lobster-trace: LRM.Conditional_Expression
1232 # lobster-trace: LRM.Restricted_Null
1233 assert isinstance(scope, ast.Scope)
1235 self.match_kw("if")
1236 t_if = self.ct
1237 if_cond = self.parse_expression(scope)
1238 self.match_kw("then")
1239 t_then = self.ct
1240 if_expr = self.parse_expression(scope)
1241 if if_expr.typ is None:
1242 self.mh.error(if_expr.location, "null is not permitted here")
1243 if_action = ast.Action(self.mh, t_if, if_cond, if_expr)
1245 rv = ast.Conditional_Expression(location=t_if.location, if_action=if_action)
1246 if_action.set_ast_link(t_if)
1247 if_action.set_ast_link(t_then)
1249 while self.peek_kw("elsif"):
1250 self.match_kw("elsif")
1251 t_elsif = self.ct
1252 elsif_cond = self.parse_expression(scope)
1253 self.match_kw("then")
1254 t_then = self.ct
1255 elsif_expr = self.parse_expression(scope)
1256 elsif_action = ast.Action(self.mh, t_elsif, elsif_cond, elsif_expr)
1257 elsif_action.set_ast_link(t_elsif)
1258 elsif_action.set_ast_link(t_then)
1259 rv.add_elsif(self.mh, elsif_action)
1261 self.match_kw("else")
1262 rv.set_ast_link(self.ct)
1263 else_expr = self.parse_expression(scope)
1264 rv.set_else_part(self.mh, else_expr)
1266 return rv
1268 def parse_builtin(self, scope, n_name, t_name):
1269 # lobster-trace: LRM.Builtin_Functions
1270 # lobster-trace: LRM.Builtin_Type_Conversion_Functions
1271 assert isinstance(scope, ast.Scope)
1272 assert isinstance(n_name, (ast.Builtin_Function, ast.Builtin_Numeric_Type))
1273 assert isinstance(t_name, Token)
1275 # Parse the arguments.
1276 parameters = []
1277 n_name.set_ast_link(self.ct)
1278 self.match("BRA")
1279 n_name.set_ast_link(self.ct)
1280 while not self.peek("KET"): 1280 ↛ 1291line 1280 didn't jump to line 1291 because the condition on line 1280 was always true
1281 exp = self.parse_expression(scope)
1282 if not self.ct.ast_link: 1282 ↛ 1283line 1282 didn't jump to line 1283 because the condition on line 1282 was never true
1283 exp.set_ast_link(self.ct)
1284 parameters.append(exp)
1286 if self.peek("COMMA"): 1286 ↛ 1290line 1286 didn't jump to line 1290 because the condition on line 1286 was always true
1287 self.match("COMMA")
1288 n_name.set_ast_link(self.ct)
1289 else:
1290 break
1291 self.match("KET")
1292 n_name.set_ast_link(self.ct)
1294 # Enforce arity
1295 if isinstance(n_name, ast.Builtin_Function):
1296 required_arity = n_name.arity
1297 precise = not n_name.arity_at_least
1298 else:
1299 required_arity = 1
1300 precise = True
1302 if precise:
1303 if required_arity != len(parameters):
1304 self.mh.error(
1305 t_name.location, "function requires %u parameters" % n_name.arity
1306 )
1307 else:
1308 if required_arity > len(parameters): 1308 ↛ 1309line 1308 didn't jump to line 1309 because the condition on line 1308 was never true
1309 self.mh.error(
1310 t_name.location,
1311 "function requires at least %u parameters" % n_name.arity,
1312 )
1314 # Enforce types
1315 if n_name.name == "len":
1316 if isinstance(parameters[0].typ, ast.Builtin_String):
1317 return ast.Unary_Expression(
1318 mh=self.mh,
1319 location=t_name.location,
1320 typ=self.builtin_int,
1321 operator=ast.Unary_Operator.STRING_LENGTH,
1322 n_operand=parameters[0],
1323 )
1324 else:
1325 return ast.Unary_Expression(
1326 mh=self.mh,
1327 location=t_name.location,
1328 typ=self.builtin_int,
1329 operator=ast.Unary_Operator.ARRAY_LENGTH,
1330 n_operand=parameters[0],
1331 )
1333 elif n_name.name in ("startswith", "endswith"):
1334 return ast.Binary_Expression(
1335 mh=self.mh,
1336 location=t_name.location,
1337 typ=self.builtin_bool,
1338 operator=(
1339 ast.Binary_Operator.STRING_STARTSWITH
1340 if "startswith" in n_name.name
1341 else ast.Binary_Operator.STRING_ENDSWITH
1342 ),
1343 n_lhs=parameters[0],
1344 n_rhs=parameters[1],
1345 )
1347 elif n_name.name == "matches":
1348 parameters[1].ensure_type(self.mh, ast.Builtin_String)
1349 try:
1350 # lobster-trace: LRM.Static_Regular_Expression
1351 # scope is None on purpose to enforce static context
1352 value = parameters[1].evaluate(self.mh, None, None)
1353 assert isinstance(value.typ, ast.Builtin_String)
1354 re.compile(value.value)
1355 except re.error as err:
1356 self.mh.error(value.location, str(err))
1357 return ast.Binary_Expression(
1358 mh=self.mh,
1359 location=t_name.location,
1360 typ=self.builtin_bool,
1361 operator=ast.Binary_Operator.STRING_REGEX,
1362 n_lhs=parameters[0],
1363 n_rhs=parameters[1],
1364 )
1366 elif n_name.name == "oneof":
1367 return ast.OneOf_Expression(
1368 mh=self.mh,
1369 location=t_name.location,
1370 typ=self.builtin_bool,
1371 choices=parameters,
1372 )
1374 elif isinstance(n_name, ast.Builtin_Numeric_Type):
1375 parameters[0].ensure_type(self.mh, ast.Builtin_Numeric_Type)
1376 if isinstance(n_name, ast.Builtin_Integer):
1377 return ast.Unary_Expression(
1378 mh=self.mh,
1379 location=t_name.location,
1380 typ=self.builtin_int,
1381 operator=ast.Unary_Operator.CONVERSION_TO_INT,
1382 n_operand=parameters[0],
1383 )
1384 elif isinstance(n_name, ast.Builtin_Decimal):
1385 return ast.Unary_Expression(
1386 mh=self.mh,
1387 location=t_name.location,
1388 typ=self.builtin_decimal,
1389 operator=ast.Unary_Operator.CONVERSION_TO_DECIMAL,
1390 n_operand=parameters[0],
1391 )
1392 else:
1393 self.mh.ice_loc(t_name.location, "unexpected type conversion")
1395 else:
1396 self.mh.ice_loc(t_name.location, "unexpected builtin")
1398 def parse_name(self, scope):
1399 # lobster-trace: LRM.Names
1401 # This is a bit more complex. The grammar is:
1402 #
1403 # qualified_name ::= [ IDENTIFIER_package_name '.' ] IDENTIFIER_name
1404 #
1405 # name ::= qualified_name
1406 # | name '.' IDENTIFIER
1407 # | name '[' expression ']'
1408 # | name '(' parameter_list ')'
1409 #
1410 # parameter_list ::= expression { ',' expression }
1412 assert isinstance(scope, ast.Scope)
1414 # All names start with a (qualified) identifier. We parse that
1415 # first. There is a special complication for functions, as
1416 # builtin functions (e.g. len) can shadow record
1417 # components. However as functions cannot be stored in
1418 # components the true grammar for function calls is always
1419 # IDENTIFIER '('; so we can slightly special case this.
1421 # lobster-trace: LRM.Builtin_Functions
1422 # lobster-trace: LRM.Builtin_Type_Conversion_Functions
1423 self.match("IDENTIFIER")
1424 if self.peek("BRA"):
1425 # If we follow our name with brackets
1426 # immediately, we have a builtin function call.
1427 n_name = self.stab.lookup(self.mh, self.ct)
1428 if not isinstance(n_name, (ast.Builtin_Function, ast.Builtin_Numeric_Type)): 1428 ↛ 1429line 1428 didn't jump to line 1429 because the condition on line 1428 was never true
1429 self.mh.error(
1430 self.ct.location, "not a valid builtin function or numeric type"
1431 )
1432 else:
1433 n_name = self.parse_qualified_name(scope, match_ident=False)
1435 # Enum literals are a bit different, so we deal with them
1436 # first.
1437 if isinstance(n_name, ast.Enumeration_Type):
1438 n_name.set_ast_link(self.ct)
1439 self.match("DOT")
1440 n_name.set_ast_link(self.ct)
1441 self.match("IDENTIFIER")
1442 lit = n_name.literals.lookup(self.mh, self.ct, ast.Enumeration_Literal_Spec)
1443 enum_lit = ast.Enumeration_Literal(location=self.ct.location, literal=lit)
1444 enum_lit.set_ast_link(self.ct)
1445 return enum_lit
1447 # Anything that remains is either a function call or an actual
1448 # name. Let's just enforce this for sanity.
1449 if not isinstance( 1449 ↛ 1458line 1449 didn't jump to line 1458 because the condition on line 1449 was never true
1450 n_name,
1451 (
1452 ast.Builtin_Function,
1453 ast.Builtin_Numeric_Type,
1454 ast.Composite_Component,
1455 ast.Quantified_Variable,
1456 ),
1457 ):
1458 self.mh.error(
1459 self.ct.location,
1460 "%s %s is not a valid name" % (n_name.__class__.__name__, n_name.name),
1461 )
1463 # Right now function calls and type conversions must be
1464 # top-level, so let's get these out of the way as well.
1465 if isinstance(n_name, (ast.Builtin_Function, ast.Builtin_Numeric_Type)):
1466 # lobster-trace: LRM.Builtin_Functions
1467 # lobster-trace: LRM.Builtin_Type_Conversion_Functions
1468 return self.parse_builtin(scope, n_name, self.ct)
1470 assert isinstance(n_name, (ast.Composite_Component, ast.Quantified_Variable))
1472 # We now process the potentially recursive part:
1473 # | name '.' IDENTIFIER
1474 # | name '[' expression ']'
1475 n_name = ast.Name_Reference(location=self.ct.location, entity=n_name)
1476 n_name.set_ast_link(self.ct)
1477 while self.peek("DOT") or self.peek("S_BRA"):
1478 if self.peek("DOT"):
1479 if not isinstance( 1479 ↛ 1483line 1479 didn't jump to line 1483 because the condition on line 1479 was never true
1480 n_name.typ, (ast.Tuple_Type, ast.Record_Type, ast.Union_Type)
1481 ):
1482 # lobster-trace: LRM.Valid_Index_Prefixes
1483 self.mh.error(
1484 n_name.location,
1485 "expression '%s' has type %s, "
1486 "which is not a tuple, record, or union"
1487 % (n_name.to_string(), n_name.typ.name),
1488 )
1490 self.match("DOT")
1491 t_dot = self.ct
1492 self.match("IDENTIFIER")
1493 t_field = self.ct
1495 is_union_access = isinstance(n_name.typ, ast.Union_Type)
1496 is_universal = True
1498 if is_union_access:
1499 # lobster-trace: LRM.Union_Type_Field_Access
1500 # lobster-trace: LRM.Union_Type_Field_Type_Conflict
1501 # lobster-trace: LRM.Union_Type_Field_Access_Validity
1502 field_name = t_field.value
1503 field_map = n_name.typ.get_field_map()
1504 if field_name not in field_map:
1505 self.mh.error(
1506 t_field.location,
1507 "field %s does not exist in any member"
1508 " of union type %s" % (field_name, n_name.typ.name),
1509 )
1510 info = field_map[field_name]
1511 if info["n_typ"] is None:
1512 self.mh.error(
1513 t_field.location,
1514 "field %s has conflicting types in"
1515 " members of union type %s" % (field_name, n_name.typ.name),
1516 )
1517 is_universal = info["count"] == info["total"]
1518 # lobster-trace: LRM.Union_Type_Partial_Field_Access
1519 if self.lint_mode and not is_universal:
1520 self.mh.check(
1521 t_field.location,
1522 "field %s exists only in %u of %u"
1523 " members of union type %s;"
1524 " accessing it on other members"
1525 " returns null"
1526 % (
1527 field_name,
1528 info["count"],
1529 info["total"],
1530 n_name.typ.name,
1531 ),
1532 "union_partial_field_access",
1533 )
1534 n_field = info["component"]
1535 else:
1536 n_field = n_name.typ.components.lookup(
1537 self.mh, t_field, ast.Composite_Component
1538 )
1540 n_field.set_ast_link(t_field)
1541 n_name = ast.Field_Access_Expression(
1542 mh=self.mh,
1543 location=t_field.location,
1544 n_prefix=n_name,
1545 n_field=n_field,
1546 is_union_access=is_union_access,
1547 is_universal=is_universal,
1548 )
1549 n_name.set_ast_link(t_dot)
1551 elif self.peek("S_BRA"): 1551 ↛ 1477line 1551 didn't jump to line 1477 because the condition on line 1551 was always true
1552 if not isinstance(n_name.typ, ast.Array_Type):
1553 self.mh.error(
1554 n_name.location,
1555 "expression '%s' has type %s, "
1556 "which is not an array" % (n_name.to_string(), n_name.typ.name),
1557 )
1559 self.match("S_BRA")
1560 t_bracket = self.ct
1561 n_index = self.parse_expression(scope)
1562 self.match("S_KET")
1563 a_binary = ast.Binary_Operator.INDEX
1564 t_bracket.ast_link = a_binary
1565 self.ct.ast_link = a_binary
1567 n_name = ast.Binary_Expression(
1568 mh=self.mh,
1569 location=t_bracket.location,
1570 typ=n_name.typ.element_type,
1571 operator=a_binary,
1572 n_lhs=n_name,
1573 n_rhs=n_index,
1574 )
1576 return n_name
1578 def parse_check_block(self):
1579 # lobster-trace: LRM.Check_Block
1580 t_severity = None
1581 self.match_kw("checks")
1582 t_checks = self.ct
1583 self.match("IDENTIFIER")
1584 # lobster-trace: LRM.Applicable_Types
1585 # lobster-trace: LRM.Applicable_Components
1586 n_ctype = self.cu.package.symbols.lookup(self.mh, self.ct, ast.Composite_Type)
1587 n_check_block = ast.Check_Block(location=self.ct.location, n_typ=n_ctype)
1588 n_check_block.set_ast_link(t_checks)
1589 n_ctype.set_ast_link(self.ct)
1590 scope = ast.Scope()
1591 scope.push(self.stab)
1592 scope.push(self.cu.package.symbols)
1593 scope.push(n_ctype.components)
1594 self.match("C_BRA")
1595 n_check_block.set_ast_link(self.ct)
1596 while not self.peek("C_KET"):
1597 c_expr = self.parse_expression(scope)
1598 if not isinstance(c_expr.typ, ast.Builtin_Boolean): 1598 ↛ 1599line 1598 didn't jump to line 1599 because the condition on line 1598 was never true
1599 self.mh.error(c_expr.location, "check expression must be Boolean")
1601 self.match("COMMA")
1602 t_first_comma = self.ct
1603 if self.peek("KEYWORD"):
1604 self.match("KEYWORD")
1605 t_severity = self.ct
1606 if self.ct.value not in ("warning", "error", "fatal"): 1606 ↛ 1607line 1606 didn't jump to line 1607 because the condition on line 1606 was never true
1607 self.mh.error(self.ct.location, "expected warning|error|fatal")
1608 c_sev = self.ct.value
1609 else:
1610 c_sev = "error"
1612 self.match("STRING")
1613 if "\n" in self.ct.value:
1614 # lobster-trace: LRM.No_Newlines_In_Message
1615 self.mh.error(
1616 self.ct.location,
1617 "error message must not contain a newline",
1618 fatal=False,
1619 )
1620 t_msg = self.ct
1622 has_extrainfo = False
1623 has_anchor = False
1624 if self.peek("COMMA"):
1625 self.match("COMMA")
1626 t_second_comma = self.ct
1627 if self.peek("IDENTIFIER"):
1628 has_anchor = True
1629 elif self.peek("STRING"): 1629 ↛ 1632line 1629 didn't jump to line 1632 because the condition on line 1629 was always true
1630 has_extrainfo = True
1631 else:
1632 self.mh.error(
1633 self.nt.location,
1634 "expected either a details string or"
1635 " identifier to anchor the check message",
1636 )
1638 if has_extrainfo:
1639 self.match("STRING")
1640 t_extrainfo = self.ct
1641 c_extrainfo = self.ct.value
1643 if self.peek("COMMA"): 1643 ↛ 1644line 1643 didn't jump to line 1644 because the condition on line 1643 was never true
1644 self.match("COMMA")
1645 t_third_comma = self.ct
1646 has_anchor = True
1648 else:
1649 c_extrainfo = None
1651 if has_anchor:
1652 self.match("IDENTIFIER")
1653 t_anchor = self.ct
1654 c_anchor = n_ctype.components.lookup(
1655 self.mh, self.ct, ast.Composite_Component
1656 )
1657 else:
1658 c_anchor = None
1660 n_check = ast.Check(
1661 n_type=n_ctype,
1662 n_expr=c_expr,
1663 n_anchor=c_anchor,
1664 severity=c_sev,
1665 t_message=t_msg,
1666 extrainfo=c_extrainfo,
1667 )
1669 # pylint: disable=possibly-used-before-assignment
1670 # pylint: disable=used-before-assignment
1672 n_check.set_ast_link(t_first_comma)
1673 if t_severity:
1674 n_check.set_ast_link(t_severity)
1675 n_check.set_ast_link(t_msg)
1676 if c_extrainfo or c_anchor:
1677 n_check.set_ast_link(t_second_comma)
1678 if c_extrainfo:
1679 n_check.set_ast_link(t_extrainfo)
1680 if c_anchor:
1681 c_anchor.set_ast_link(t_anchor)
1682 if c_anchor and c_extrainfo: 1682 ↛ 1683line 1682 didn't jump to line 1683 because the condition on line 1682 was never true
1683 n_check.set_ast_link(t_third_comma)
1685 n_ctype.add_check(n_check)
1686 n_check_block.add_check(n_check)
1688 assert scope.size() == 3
1690 self.match("C_KET")
1691 n_check_block.set_ast_link(self.ct)
1693 return n_check_block
1695 def parse_section_declaration(self):
1696 # lobster-trace: LRM.Section_Declaration
1697 self.match_kw("section")
1698 t_section = self.ct
1699 self.match("STRING")
1700 sec = ast.Section(
1701 name=self.ct.value,
1702 location=self.ct.location,
1703 parent=self.section[-1] if self.section else None,
1704 )
1705 sec.set_ast_link(self.ct)
1706 sec.set_ast_link(t_section)
1707 self.section.append(sec)
1708 self.match("C_BRA")
1709 sec.set_ast_link(self.ct)
1710 while not self.peek("C_KET"):
1711 self.parse_trlc_entry()
1712 self.match("C_KET")
1713 sec.set_ast_link(self.ct)
1714 self.section.pop()
1716 def parse_boolean(self):
1717 # lobster-trace: LRM.Boolean_Values
1718 self.match("KEYWORD")
1719 if self.ct.value in ("true", "false"): 1719 ↛ 1722line 1719 didn't jump to line 1722 because the condition on line 1719 was always true
1720 return ast.Boolean_Literal(self.ct, self.builtin_bool)
1721 else:
1722 self.mh.error(self.ct.location, "expected boolean literal (true or false)")
1724 def parse_value(self, typ):
1725 # lobster-trace: LRM.Tuple_Syntax_Correct_Form
1726 assert isinstance(typ, ast.Type)
1728 if isinstance(typ, ast.Builtin_Numeric_Type):
1729 # lobster-trace: LRM.Integer_Values
1730 # lobster-trace: LRM.Decimal_Values
1731 if self.peek("OPERATOR") and self.nt.value in Parser.ADDING_OPERATOR:
1732 self.match("OPERATOR")
1733 t_op = self.ct
1734 e_op = (
1735 ast.Unary_Operator.PLUS
1736 if t_op.value == "+"
1737 else ast.Unary_Operator.MINUS
1738 )
1739 t_op.ast_link = e_op
1740 else:
1741 t_op = None
1743 if isinstance(typ, ast.Builtin_Decimal):
1744 self.match("DECIMAL")
1745 rv = ast.Decimal_Literal(self.ct, self.builtin_decimal)
1746 rv.set_ast_link(self.ct)
1747 elif isinstance(typ, ast.Builtin_Integer):
1748 self.match("INTEGER")
1749 rv = ast.Integer_Literal(self.ct, self.builtin_int)
1750 rv.set_ast_link(self.ct)
1751 else:
1752 assert False
1754 if t_op:
1755 rv = ast.Unary_Expression(
1756 mh=self.mh,
1757 location=t_op.location,
1758 typ=rv.typ,
1759 operator=e_op,
1760 n_operand=rv,
1761 )
1763 return rv
1765 elif isinstance(typ, ast.Builtin_Markup_String):
1766 # lobster-trace: LRM.Markup_String_Values
1767 return self.parse_markup_string()
1769 elif isinstance(typ, ast.Builtin_String):
1770 # lobster-trace: LRM.String_Values
1771 self.match("STRING")
1772 rv = ast.String_Literal(self.ct, self.builtin_str)
1773 rv.set_ast_link(self.ct)
1774 return rv
1776 elif isinstance(typ, ast.Builtin_Boolean):
1777 rv = self.parse_boolean()
1778 rv.set_ast_link(self.ct)
1779 return rv
1781 elif isinstance(typ, ast.Array_Type):
1782 self.match("S_BRA")
1783 rv = ast.Array_Aggregate(self.ct.location, typ)
1784 rv.set_ast_link(self.ct)
1785 while not self.peek("S_KET"):
1786 array_elem = self.parse_value(typ.element_type)
1787 rv.append(array_elem)
1788 if self.peek("COMMA"):
1789 self.match("COMMA")
1790 rv.set_ast_link(self.ct)
1791 elif self.peek("S_KET") or self.nt is None: 1791 ↛ anywhereline 1791 didn't jump anywhere: it always raised an exception.
1792 break
1793 else:
1794 self.mh.error(
1795 self.ct.location,
1796 "comma separating array elements is missing",
1797 fatal=False,
1798 )
1800 self.match("S_KET")
1801 rv.set_ast_link(self.ct)
1803 if len(rv.value) < typ.lower_bound:
1804 self.mh.error(
1805 self.ct.location,
1806 "this array requires at least %u elements "
1807 "(only %u provided)" % (typ.lower_bound, len(rv.value)),
1808 fatal=False,
1809 )
1810 if typ.upper_bound and len(rv.value) > typ.upper_bound:
1811 self.mh.error(
1812 rv.value[typ.upper_bound].location,
1813 "this array requires at most %u elements "
1814 "(%u provided)" % (typ.upper_bound, len(rv.value)),
1815 fatal=False,
1816 )
1818 return rv
1820 elif isinstance(typ, ast.Enumeration_Type):
1821 enum = self.parse_qualified_name(self.default_scope, ast.Enumeration_Type)
1822 enum.set_ast_link(self.ct)
1823 if enum != typ:
1824 self.mh.error(self.ct.location, "expected %s" % typ.name)
1825 self.match("DOT")
1826 enum.set_ast_link(self.ct)
1827 self.match("IDENTIFIER")
1828 lit = enum.literals.lookup(self.mh, self.ct, ast.Enumeration_Literal_Spec)
1829 return ast.Enumeration_Literal(self.ct.location, lit)
1831 elif isinstance(typ, (ast.Record_Type, ast.Union_Type)):
1832 self.match("IDENTIFIER")
1833 t_name = self.ct
1834 if self.peek("DOT"):
1835 self.match("DOT")
1836 t_dot = self.ct
1837 self.match("IDENTIFIER")
1838 the_pkg = self.stab.lookup(self.mh, t_name, ast.Package)
1839 the_pkg.set_ast_link(t_name)
1840 the_pkg.set_ast_link(t_dot)
1841 if not self.cu.is_visible(the_pkg): 1841 ↛ 1842line 1841 didn't jump to line 1842 because the condition on line 1841 was never true
1842 self.mh.error(
1843 self.ct.location, "package must be imported before use"
1844 )
1845 t_name = self.ct
1846 else:
1847 the_pkg = self.cu.package
1849 rv = ast.Record_Reference(
1850 location=t_name.location, name=t_name.value, typ=typ, package=the_pkg
1851 )
1852 rv.set_ast_link(t_name)
1854 # We can do an early lookup if the target is known
1855 if the_pkg.symbols.contains(t_name.value):
1856 rv.resolve_references(self.mh)
1858 return rv
1860 elif isinstance(typ, ast.Tuple_Type) and typ.has_separators():
1861 # lobster-trace: LRM.Tuple_Separator_Form
1862 rv = ast.Tuple_Aggregate(self.nt.location, typ)
1864 next_is_optional = False
1865 for n_item in typ.iter_sequence():
1866 if isinstance(n_item, ast.Composite_Component):
1867 if next_is_optional and n_item.optional:
1868 break
1869 value = self.parse_value(n_item.n_typ)
1870 rv.assign(n_item.name, value)
1872 elif n_item.token.kind in ("AT", "COLON", "SEMICOLON"):
1873 if self.peek(n_item.token.kind):
1874 self.match(n_item.token.kind)
1875 n_item.set_ast_link(self.ct)
1876 else:
1877 next_is_optional = True
1879 elif n_item.token.kind == "IDENTIFIER":
1880 if self.peek("IDENTIFIER") and self.nt.value == n_item.token.value:
1881 self.match("IDENTIFIER")
1882 n_item.set_ast_link(self.ct)
1883 else:
1884 next_is_optional = True
1886 else:
1887 assert False
1889 return rv
1891 elif isinstance(typ, ast.Tuple_Type) and not typ.has_separators():
1892 # lobster-trace: LRM.Tuple_Generic_Form
1893 self.match("BRA")
1894 rv = ast.Tuple_Aggregate(self.ct.location, typ)
1895 rv.set_ast_link(self.ct)
1897 first = True
1898 for n_field in typ.iter_sequence():
1899 if first:
1900 first = False
1901 else:
1902 self.match("COMMA")
1903 rv.set_ast_link(self.ct)
1904 rv.assign(n_field.name, self.parse_value(n_field.n_typ))
1906 self.match("KET")
1907 rv.set_ast_link(self.ct)
1908 return rv
1910 else:
1911 self.mh.ice_loc(
1912 self.ct.location,
1913 "logic error: unexpected type %s" % typ.__class__.__name__,
1914 )
1916 def parse_markup_string(self):
1917 # lobster-trace: LRM.Markup_String_Values
1918 self.match("STRING")
1919 rv = ast.String_Literal(self.ct, self.builtin_mstr)
1920 mpar = Markup_Parser(self, rv)
1921 mpar.parse_all_references()
1922 return rv
1924 def parse_record_object_declaration(self):
1925 # lobster-trace: LRM.Section_Declaration
1926 # lobster-trace: LRM.Record_Object_Declaration
1927 # lobster-trace: LRM.Valid_Record_Types
1928 # lobster-trace: LRM.Valid_Components
1929 # lobster-trace: LRM.Valid_Enumeration_Literals
1930 # lobster-trace: LRM.Mandatory_Components
1931 # lobster-trace: LRM.Evaluation_Of_Checks
1932 # lobster-trace: LRM.Single_Value_Assignment
1934 r_typ = self.parse_qualified_name(self.default_scope, ast.Record_Type)
1935 r_typ.set_ast_link(self.ct)
1936 # lobster-trace: LRM.Abstract_Types
1937 if r_typ.is_abstract:
1938 self.mh.error(
1939 self.ct.location,
1940 "cannot declare object of abstract record type %s" % r_typ.name,
1941 )
1943 self.match("IDENTIFIER")
1944 obj = ast.Record_Object(
1945 name=self.ct.value,
1946 location=self.ct.location,
1947 n_typ=r_typ,
1948 section=self.section.copy() if self.section else None,
1949 n_package=self.cu.package,
1950 )
1951 self.cu.package.symbols.register(self.mh, obj)
1952 obj.set_ast_link(self.ct)
1954 self.match("C_BRA")
1955 obj.set_ast_link(self.ct)
1956 while not self.peek("C_KET"):
1957 self.match("IDENTIFIER")
1958 comp = r_typ.components.lookup(self.mh, self.ct, ast.Composite_Component)
1959 if obj.is_component_implicit_null(comp):
1960 self.mh.error(
1961 self.ct.location,
1962 "component '%s' already assigned at line %i"
1963 % (comp.name, obj.field[comp.name].location.line_no),
1964 )
1965 comp.set_ast_link(self.ct)
1966 if r_typ.is_frozen(comp):
1967 self.mh.error(
1968 self.ct.location, "cannot overwrite frozen component %s" % comp.name
1969 )
1970 self.match("ASSIGN")
1971 comp.set_ast_link(self.ct)
1972 value = self.parse_value(comp.n_typ)
1973 if not self.ct.ast_link:
1974 value.set_ast_link(self.ct)
1975 obj.assign(comp, value)
1977 # Check that each non-optional component has been specified
1978 for comp in r_typ.all_components():
1979 if isinstance(obj.field[comp.name], ast.Implicit_Null):
1980 if r_typ.is_frozen(comp):
1981 obj.assign(comp, r_typ.get_freezing_expression(comp))
1982 elif not comp.optional:
1983 self.mh.error(
1984 obj.location,
1985 "required component %s (see %s) is not defined"
1986 % (comp.name, self.mh.cross_file_reference(comp.location)),
1987 )
1989 self.match("C_KET")
1990 obj.set_ast_link(self.ct)
1992 return obj
1994 def parse_trlc_entry(self):
1995 # lobster-trace: LRM.TRLC_File
1996 if self.peek_kw("section"):
1997 self.parse_section_declaration()
1998 else:
1999 self.cu.add_item(self.parse_record_object_declaration())
2001 def parse_preamble(self, kind):
2002 assert kind in ("rsl", "trlc")
2003 # lobster-trace: LRM.Layout
2004 # lobster-trace: LRM.Preamble
2006 # First, parse package indication, declaring the package if
2007 # needed
2008 self.match_kw("package")
2009 t_pkg = self.ct
2010 self.match("IDENTIFIER")
2012 if kind == "rsl":
2013 declare_package = True
2014 else:
2015 # lobster-trace: LRM.Late_Package_Declarations
2016 declare_package = not self.stab.contains(self.ct.value)
2018 if declare_package:
2019 # lobster-trace: LRM.Package_Declaration
2020 pkg = ast.Package(
2021 name=self.ct.value,
2022 location=self.ct.location,
2023 builtin_stab=self.stab,
2024 declared_late=kind == "trlc",
2025 )
2026 self.stab.register(self.mh, pkg)
2027 else:
2028 pkg = self.stab.lookup(self.mh, self.ct, ast.Package)
2030 pkg.set_ast_link(t_pkg)
2031 pkg.set_ast_link(self.ct)
2033 # lobster-trace: LRM.Current_Package
2034 self.cu.set_package(pkg)
2036 self.default_scope.push(self.cu.package.symbols)
2038 # Second, parse import list (but don't resolve names yet)
2039 # lobster-trace: LRM.Import_Visibility
2040 if kind != "check": 2040 ↛ exitline 2040 didn't return from function 'parse_preamble' because the condition on line 2040 was always true
2041 while self.peek_kw("import"):
2042 self.match_kw("import")
2043 pkg.set_ast_link(self.ct)
2044 self.match("IDENTIFIER")
2045 self.cu.add_import(self.mh, self.ct)
2047 def parse_rsl_file(self):
2048 # lobster-trace: LRM.RSL_File
2049 assert self.cu.package is not None
2051 ok = True
2052 while not self.peek_eof():
2053 try:
2054 if self.peek_kw("checks"):
2055 self.cu.add_item(self.parse_check_block())
2056 else:
2057 self.cu.add_item(self.parse_type_declaration())
2058 except TRLC_Error as err:
2059 if not self.error_recovery or err.kind == "lex error": 2059 ↛ 2060line 2059 didn't jump to line 2060 because the condition on line 2059 was never true
2060 raise
2062 ok = False
2064 # Recovery strategy is to scan until we get the next
2065 # relevant keyword
2066 self.skip_until_newline()
2067 while not self.peek_eof():
2068 if (
2069 self.peek_kw("checks")
2070 or self.peek_kw("type")
2071 or self.peek_kw("abstract")
2072 or self.peek_kw("final")
2073 or self.peek_kw("tuple")
2074 or self.peek_kw("enum")
2075 ):
2076 break
2077 self.advance()
2078 self.skip_until_newline()
2080 self.match_eof()
2082 for tok in self.lexer.tokens:
2083 if tok.kind == "COMMENT":
2084 self.cu.package.set_ast_link(tok)
2086 return ok
2088 def parse_trlc_file(self):
2089 # lobster-trace: LRM.TRLC_File
2090 assert self.cu.package is not None
2092 ok = True
2094 while self.peek_kw("section") or self.peek("IDENTIFIER"):
2095 try:
2096 self.parse_trlc_entry()
2097 except TRLC_Error as err:
2098 if not self.error_recovery or err.kind == "lex error": 2098 ↛ 2099line 2098 didn't jump to line 2099 because the condition on line 2098 was never true
2099 raise
2101 ok = False
2103 # Recovery strategy is to keep going until we find an
2104 # identifier that is a package or type, or section, or
2105 # EOF
2106 self.skip_until_newline()
2107 while not self.peek_eof():
2108 if self.peek_kw("section"): 2108 ↛ 2109line 2108 didn't jump to line 2109 because the condition on line 2108 was never true
2109 break
2110 elif not self.peek("IDENTIFIER"):
2111 pass
2112 elif self.stab.contains(self.nt.value): 2112 ↛ 2113line 2112 didn't jump to line 2113 because the condition on line 2112 was never true
2113 n_sym = self.stab.lookup_assuming(self.mh, self.nt.value)
2114 if isinstance(n_sym, ast.Package):
2115 break
2116 elif self.cu.package.symbols.contains(self.nt.value):
2117 n_sym = self.cu.package.symbols.lookup_assuming(
2118 self.mh, self.nt.value
2119 )
2120 if isinstance(n_sym, ast.Record_Type):
2121 break
2122 self.advance()
2123 self.skip_until_newline()
2125 self.match_eof()
2127 for tok in self.lexer.tokens:
2128 if tok.kind == "COMMENT":
2129 self.cu.package.set_ast_link(tok)
2131 return ok