Coverage for trlc/parser.py: 96%
1121 statements
« prev ^ index » next coverage.py v7.10.7, created at 2026-05-04 06:55 +0000
« prev ^ index » next coverage.py v7.10.7, created at 2026-05-04 06:55 +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(self.source_location(start_line,
75 start_col,
76 start_pos,
77 start_pos + 1),
78 "cannot nest reference lists")
79 else:
80 self.in_reflist = True
82 elif self.cc == "]" and self.nc == "]":
83 kind = "REFLIST_END"
84 self.advance()
85 if self.in_reflist:
86 self.in_reflist = False
87 else:
88 self.mh.lex_error(self.source_location(start_line,
89 start_col,
90 start_pos,
91 start_pos + 1),
92 "opening [[ for this ]] found")
94 elif not self.in_reflist:
95 kind = "CHARACTER"
97 elif self.cc == ",":
98 kind = "REFLIST_COMMA"
100 elif self.cc == ".":
101 kind = "REFLIST_DOT"
103 elif self.is_alpha(self.cc): 103 ↛ 110line 103 didn't jump to line 110 because the condition on line 103 was always true
104 kind = "REFLIST_IDENTIFIER"
105 while self.nc and (self.is_alnum(self.nc) or
106 self.nc == "_"):
107 self.advance()
109 else:
110 self.mh.lex_error(self.source_location(start_line,
111 start_col,
112 start_pos,
113 start_pos),
114 "unexpected character '%s'" % self.cc)
116 loc = self.source_location(start_line,
117 start_col,
118 start_pos,
119 self.lexpos)
121 # pylint: disable=possibly-used-before-assignment
122 return Markup_Token(loc,
123 kind,
124 self.content[start_pos:self.lexpos + 1])
127class Parser_Base:
128 def __init__(self, mh, lexer, eoc_name, token_map, keywords):
129 assert isinstance(mh, Message_Handler)
130 assert isinstance(lexer, Lexer_Base)
131 assert isinstance(eoc_name, str)
132 assert isinstance(token_map, dict)
133 assert isinstance(keywords, frozenset)
134 self.mh = mh
135 self.lexer = lexer
137 self.eoc_name = eoc_name
138 self.language_tokens = token_map
139 self.language_keywords = keywords
141 self.ct = None
142 self.nt = None
143 self.advance()
145 def advance(self):
146 # lobster-trace: LRM.Comments
147 self.ct = self.nt
148 while True:
149 self.nt = self.lexer.token()
150 if self.nt is None or self.nt.kind != "COMMENT":
151 break
153 def skip_until_newline(self):
154 if self.ct is None: 154 ↛ 155line 154 didn't jump to line 155 because the condition on line 154 was never true
155 return
156 current_line = self.ct.location.line_no
157 while self.nt and self.nt.location.line_no == current_line:
158 self.advance()
160 def peek(self, kind):
161 assert kind in self.language_tokens, \
162 "%s is not a valid token" % kind
163 return self.nt is not None and self.nt.kind == kind
165 def peek_eof(self):
166 return self.nt is None
168 def peek_kw(self, value):
169 assert value in self.language_keywords, \
170 "%s is not a valid keyword" % value
171 return self.peek("KEYWORD") and self.nt.value == value
173 def match(self, kind):
174 # lobster-trace: LRM.Matching_Value_Types
176 assert kind in self.language_tokens, \
177 "%s is not a valid token" % kind
178 if self.nt is None:
179 if self.ct is None: 179 ↛ 180line 179 didn't jump to line 180 because the condition on line 179 was never true
180 self.mh.error(self.lexer.file_location(),
181 "expected %s, encountered %s instead" %
182 (self.language_tokens[kind], self.eoc_name))
183 else:
184 self.mh.error(self.ct.location,
185 "expected %s, encountered %s instead" %
186 (self.language_tokens[kind], self.eoc_name))
187 elif self.nt.kind != kind:
188 self.mh.error(self.nt.location,
189 "expected %s, encountered %s instead" %
190 (self.language_tokens[kind],
191 self.language_tokens[self.nt.kind]))
192 self.advance()
194 def match_eof(self):
195 if self.nt is not None: 195 ↛ 196line 195 didn't jump to line 196 because the condition on line 195 was never true
196 self.mh.error(self.nt.location,
197 "expected %s, encountered %s instead" %
198 (self.eoc_name,
199 self.language_tokens[self.nt.kind]))
201 def match_kw(self, value):
202 assert value in self.language_keywords, \
203 "%s is not a valid keyword" % value
204 if self.nt is None:
205 if self.ct is None: 205 ↛ 210line 205 didn't jump to line 210 because the condition on line 205 was always true
206 self.mh.error(self.lexer.file_location(),
207 "expected keyword %s, encountered %s instead" %
208 (value, self.eoc_name))
209 else:
210 self.mh.error(self.ct.location,
211 "expected keyword %s, encountered %s instead" %
212 (value, self.eoc_name))
213 elif self.nt.kind != "KEYWORD":
214 self.mh.error(self.nt.location,
215 "expected keyword %s, encountered %s instead" %
216 (value,
217 self.language_tokens[self.nt.kind]))
218 elif self.nt.value != value:
219 self.mh.error(self.nt.location,
220 "expected keyword %s,"
221 " encountered keyword %s instead" %
222 (value, self.nt.value))
223 self.advance()
226class Markup_Parser(Parser_Base):
227 def __init__(self, parent, literal):
228 assert isinstance(parent, Parser)
229 super().__init__(parent.mh, Markup_Lexer(parent.mh, literal),
230 eoc_name = "end-of-string",
231 token_map = Markup_Token.KIND,
232 keywords = frozenset())
233 self.parent = parent
234 self.references = literal.references
236 def parse_all_references(self):
237 while self.nt:
238 if self.peek("CHARACTER"):
239 self.advance()
240 else:
241 self.parse_ref_list()
242 self.match_eof()
243 return self.references
245 def parse_ref_list(self):
246 self.match("REFLIST_BEGIN")
247 self.parse_qualified_name()
248 while self.peek("REFLIST_COMMA"):
249 self.match("REFLIST_COMMA")
250 self.parse_qualified_name()
251 self.match("REFLIST_END")
253 def parse_qualified_name(self):
254 # lobster-trace: LRM.Qualified_Name
255 # lobster-trace: LRM.Valid_Qualifier
256 # lobster-trace: LRM.Valid_Name
257 # lobster-trace: LRM.Markup_String_Resolution
258 # lobster-trace: LRM.Markup_String_Types
260 self.match("REFLIST_IDENTIFIER")
261 if self.peek("REFLIST_DOT"):
262 package = self.parent.stab.lookup_direct(
263 mh = self.mh,
264 name = self.ct.value,
265 error_location = self.ct.location,
266 required_subclass = ast.Package)
267 if not self.parent.cu.is_visible(package):
268 self.mh.error(self.ct.location,
269 "package must be imported before use")
271 self.match("REFLIST_DOT")
272 self.match("REFLIST_IDENTIFIER")
273 else:
274 package = self.parent.cu.package
276 ref = ast.Record_Reference(location = self.ct.location,
277 name = self.ct.value,
278 typ = None,
279 package = package)
280 self.references.append(ref)
283class Parser(Parser_Base):
284 COMPARISON_OPERATOR = ("==", "!=", "<", "<=", ">", ">=")
285 ADDING_OPERATOR = ("+", "-")
286 MULTIPLYING_OPERATOR = ("*", "/", "%")
288 def __init__(self,
289 mh,
290 stab,
291 file_name,
292 lint_mode,
293 error_recovery,
294 primary_file=True,
295 lexer=None):
296 assert isinstance(mh, Message_Handler)
297 assert isinstance(stab, ast.Symbol_Table)
298 assert isinstance(file_name, str)
299 assert isinstance(lint_mode, bool)
300 assert isinstance(error_recovery, bool)
301 assert isinstance(primary_file, bool)
302 assert isinstance(lexer, TRLC_Lexer) or lexer is None
303 if lexer: 303 ↛ 309line 303 didn't jump to line 309 because the condition on line 303 was always true
304 super().__init__(mh, lexer,
305 eoc_name = "end-of-file",
306 token_map = Token.KIND,
307 keywords = TRLC_Lexer.KEYWORDS)
308 else:
309 super().__init__(mh, TRLC_Lexer(mh, file_name),
310 eoc_name = "end-of-file",
311 token_map = Token.KIND,
312 keywords = TRLC_Lexer.KEYWORDS)
314 self.lint_mode = lint_mode
315 self.error_recovery = error_recovery
317 self.stab = stab
318 self.cu = ast.Compilation_Unit(file_name)
320 self.primary = primary_file
321 self.secondary = False
322 # Controls if the file is actually fully parsed: primary means
323 # it was selected on the command-line and secondary means it
324 # was selected by dependency analysis.
326 self.builtin_bool = stab.lookup_assuming(self.mh, "Boolean")
327 self.builtin_int = stab.lookup_assuming(self.mh, "Integer")
328 self.builtin_decimal = stab.lookup_assuming(self.mh, "Decimal")
329 self.builtin_str = stab.lookup_assuming(self.mh, "String")
330 self.builtin_mstr = stab.lookup_assuming(self.mh, "Markup_String")
332 self.section = []
333 self.default_scope = ast.Scope()
334 self.default_scope.push(self.stab)
336 def parse_described_name(self):
337 # lobster-trace: LRM.Described_Names
338 # lobster-trace: LRM.Described_Name_Description
339 self.match("IDENTIFIER")
340 name = self.ct
342 if self.peek("STRING"):
343 self.match("STRING")
344 t_descr = self.ct
345 return name, t_descr.value, t_descr
346 else:
347 return name, None, None
349 def parse_qualified_name(self,
350 scope,
351 required_subclass=None,
352 match_ident=True):
353 # lobster-trace: LRM.Qualified_Name
354 # lobster-trace: LRM.Valid_Qualifier
355 # lobster-trace: LRM.Valid_Name
356 assert isinstance(scope, ast.Scope)
357 assert required_subclass is None or isinstance(required_subclass, type)
358 assert isinstance(match_ident, bool)
360 if match_ident:
361 self.match("IDENTIFIER")
362 sym = scope.lookup(self.mh, self.ct)
363 sym.set_ast_link(self.ct)
365 if isinstance(sym, ast.Package):
366 if not self.cu.is_visible(sym):
367 self.mh.error(self.ct.location,
368 "package must be imported before use")
369 self.match("DOT")
370 sym.set_ast_link(self.ct)
371 self.match("IDENTIFIER")
372 return sym.symbols.lookup(self.mh, self.ct, required_subclass)
373 else:
374 # Easiest way to generate the correct error message
375 return scope.lookup(self.mh, self.ct, required_subclass)
377 def parse_type_declaration(self):
378 # lobster-trace: LRM.Type_Declarations
379 if self.peek_kw("enum"):
380 n_item = self.parse_enum_declaration()
381 elif self.peek_kw("tuple"):
382 n_item = self.parse_tuple_declaration()
383 else:
384 n_item = self.parse_record_declaration()
385 assert isinstance(n_item, ast.Concrete_Type)
386 return n_item
388 def parse_enum_declaration(self):
389 # lobster-trace: LRM.Enumeration_Declaration
390 self.match_kw("enum")
391 t_enum = self.ct
392 name, description, t_description = self.parse_described_name()
394 enum = ast.Enumeration_Type(name = name.value,
395 description = description,
396 location = name.location,
397 package = self.cu.package)
398 self.cu.package.symbols.register(self.mh, enum)
399 enum.set_ast_link(t_enum)
400 enum.set_ast_link(name)
401 if t_description:
402 enum.set_ast_link(t_description)
404 self.match("C_BRA")
405 enum.set_ast_link(self.ct)
406 empty = True
407 while not self.peek("C_KET"):
408 name, description, t_description = self.parse_described_name()
409 lit = ast.Enumeration_Literal_Spec(name = name.value,
410 description = description,
411 location = name.location,
412 enum = enum)
413 lit.set_ast_link(name)
414 if t_description:
415 lit.set_ast_link(self.ct)
416 empty = False
417 enum.literals.register(self.mh, lit)
418 self.match("C_KET")
419 enum.set_ast_link(self.ct)
421 if empty:
422 # lobster-trace: LRM.No_Empty_Enumerations
423 self.mh.error(enum.location,
424 "empty enumerations are not permitted")
426 return enum
428 def parse_tuple_field(self,
429 n_tuple,
430 optional_allowed,
431 optional_reason,
432 optional_required):
433 assert isinstance(n_tuple, ast.Tuple_Type)
434 assert isinstance(optional_allowed, bool)
435 assert isinstance(optional_reason, str)
436 assert isinstance(optional_required, bool)
437 assert optional_allowed or not optional_required
439 field_name, field_description, t_descr = self.parse_described_name()
441 if optional_required or self.peek_kw("optional"):
442 self.match_kw("optional")
443 t_optional = self.ct
444 field_is_optional = True
445 if not optional_allowed:
446 self.mh.error(self.ct.location, optional_reason)
447 else:
448 field_is_optional = False
449 t_optional = None
451 # lobster-trace: LRM.Tuple_Field_Types
452 field_type = self.parse_qualified_name(self.default_scope,
453 ast.Type)
454 comp = ast.Composite_Component(name = field_name.value,
455 description = field_description,
456 location = field_name.location,
457 member_of = n_tuple,
458 n_typ = field_type,
459 optional = field_is_optional)
460 comp.set_ast_link(field_name)
461 if t_descr:
462 comp.set_ast_link(t_descr)
463 if field_is_optional:
464 comp.set_ast_link(t_optional)
466 return comp
468 def parse_tuple_declaration(self):
469 # lobster-trace: LRM.Tuple_Declaration
470 self.match_kw("tuple")
471 t_tuple = self.ct
472 name, description, t_descr = self.parse_described_name()
474 n_tuple = ast.Tuple_Type(name = name.value,
475 description = description,
476 location = name.location,
477 package = self.cu.package)
479 n_tuple.set_ast_link(t_tuple)
480 n_tuple.set_ast_link(name)
481 if t_descr:
482 n_tuple.set_ast_link(t_descr)
483 self.match("C_BRA")
484 n_tuple.set_ast_link(self.ct)
486 n_field = self.parse_tuple_field(
487 n_tuple,
488 optional_allowed = False,
489 optional_reason = "first field may not be optional",
490 optional_required = False)
491 n_tuple.components.register(self.mh, n_field)
493 has_separators = False
494 optional_required = False
495 separator_allowed = True
497 while self.peek_kw("separator") or self.peek("IDENTIFIER"):
498 if has_separators or self.peek_kw("separator"):
499 has_separators = True
500 self.match_kw("separator")
501 t_sep = self.ct
502 if not separator_allowed:
503 # lobster-trace: LRM.Tuple_Separators_All_Or_None
504 self.mh.error(self.ct.location,
505 "either all fields must be separated,"
506 " or none")
507 if self.peek("IDENTIFIER") or \ 507 ↛ 519line 507 didn't jump to line 519 because the condition on line 507 was always true
508 self.peek("AT") or \
509 self.peek("COLON") or \
510 self.peek("SEMICOLON"):
511 self.advance()
512 sep = ast.Separator(self.ct)
513 sep.set_ast_link(t_sep)
514 sep.set_ast_link(self.ct)
515 n_tuple.add_separator(sep)
516 else:
517 separator_allowed = False
518 # lobster-trace: LRM.Tuple_Optional_Requires_Separators
519 n_field = self.parse_tuple_field(
520 n_tuple,
521 optional_allowed = has_separators,
522 optional_reason = ("optional only permitted in tuples"
523 " with separators"),
524 optional_required = optional_required)
525 n_tuple.components.register(self.mh, n_field)
526 # lobster-trace: LRM.Tuple_Optional_Fields
527 optional_required |= n_field.optional
529 self.match("C_KET")
530 n_tuple.set_ast_link(self.ct)
532 # Final check to ban tuples with separators containing other
533 # tuples.
534 if has_separators:
535 # lobster-trace: LRM.Restricted_Tuple_Nesting
536 for n_field in n_tuple.components.values():
537 if isinstance(n_field.n_typ, ast.Tuple_Type) and \
538 n_field.n_typ.has_separators():
539 self.mh.error(
540 n_field.location,
541 "tuple type %s, which contains separators,"
542 " may not contain another tuple with separators"
543 % n_tuple.name)
545 # Late registration to avoid recursion in tuples
546 # lobster-trace: LRM.Tuple_Field_Types
547 self.cu.package.symbols.register(self.mh, n_tuple)
549 return n_tuple
551 def parse_union_type(self):
552 """Parse a union type declaration: '[' Type1 ',' Type2 ... ']'
554 The leading S_BRA must be the next token when called.
555 Returns an ast.Union_Type node.
556 """
557 # lobster-trace: LRM.union_type
558 # lobster-trace: LRM.Union_Type_No_Duplicates
559 # lobster-trace: LRM.Union_Type_Record_Types_Only
560 self.match("S_BRA")
561 t_s_bra = self.ct
563 union_type_entries = [] # list of (Record_Type, Location)
564 first_type = self.parse_qualified_name(self.default_scope,
565 ast.Record_Type)
566 first_type.set_ast_link(self.ct)
567 union_type_entries.append((first_type, self.ct.location))
569 while self.peek("COMMA"):
570 self.match("COMMA")
571 next_type = self.parse_qualified_name(
572 self.default_scope,
573 ast.Record_Type)
574 next_type.set_ast_link(self.ct)
575 union_type_entries.append((next_type, self.ct.location))
577 self.match("S_KET")
578 t_s_ket = self.ct
580 seen = {}
581 for t, loc in union_type_entries:
582 fqn = t.fully_qualified_name()
583 if fqn in seen:
584 self.mh.error(loc,
585 "duplicate type %s in union" % t.name,
586 fatal=False)
587 else:
588 seen[fqn] = loc
590 union_types = [t for t, _ in union_type_entries]
591 c_typ = ast.Union_Type(
592 location = t_s_bra.location,
593 types = union_types)
594 c_typ.set_ast_link(t_s_bra)
595 c_typ.set_ast_link(t_s_ket)
596 return c_typ
598 def parse_record_component(self, n_record):
599 assert isinstance(n_record, ast.Record_Type)
601 c_name, c_descr, t_descr = self.parse_described_name()
602 t_optional = None
603 c_optional = False
604 if self.peek_kw("optional"):
605 self.match_kw("optional")
606 t_optional = self.ct
607 c_optional = True
609 # S_BRA here means a union type '[T1, T2, ...]', not array bounds.
610 # Array bounds '[INTEGER..INTEGER]' are checked in the next block.
611 if self.peek("S_BRA"):
612 c_typ = self.parse_union_type()
613 else:
614 c_typ = self.parse_qualified_name(self.default_scope,
615 ast.Type)
616 c_typ.set_ast_link(self.ct)
618 if self.peek("S_BRA"):
619 self.match("S_BRA")
620 t_s_bra = self.ct
621 self.match("INTEGER")
622 t_lo = self.ct
623 a_lo = self.ct.value
624 loc_lo = self.ct.location
625 self.match("RANGE")
626 t_range = self.ct
627 a_loc = self.ct.location
628 a_hi = None
629 if self.peek("INTEGER"):
630 self.match("INTEGER")
631 a_hi = self.ct.value
632 elif self.peek("OPERATOR") and self.nt.value == "*": 632 ↛ 635line 632 didn't jump to line 635 because the condition on line 632 was always true
633 self.match("OPERATOR")
634 else:
635 self.mh.error(self.nt.location,
636 "expected INTEGER or * for upper bound")
637 t_hi = self.ct
638 loc_hi = self.ct.location
639 self.match("S_KET")
640 t_s_ket = self.ct
641 c_typ = ast.Array_Type(location = a_loc,
642 element_type = c_typ,
643 lower_bound = a_lo,
644 upper_bound = a_hi,
645 loc_lower = loc_lo,
646 loc_upper = loc_hi)
647 c_typ.set_ast_link(t_s_bra)
648 c_typ.set_ast_link(t_lo)
649 c_typ.set_ast_link(t_range)
650 c_typ.set_ast_link(t_hi)
651 c_typ.set_ast_link(t_s_ket)
653 c_comp = ast.Composite_Component(name = c_name.value,
654 description = c_descr,
655 location = c_name.location,
656 member_of = n_record,
657 n_typ = c_typ,
658 optional = c_optional)
659 c_comp.set_ast_link(c_name)
660 if t_descr:
661 c_comp.set_ast_link(t_descr)
662 if c_optional:
663 c_comp.set_ast_link(t_optional)
665 return c_comp
667 def parse_record_declaration(self):
668 t_abstract = None
669 t_final = None
670 is_abstract = False
671 is_final = False
672 if self.peek_kw("abstract"):
673 self.match_kw("abstract")
674 t_abstract = self.ct
675 is_abstract = True
676 elif self.peek_kw("final"):
677 self.match_kw("final")
678 t_final = self.ct
679 is_final = True
681 self.match_kw("type")
682 t_type = self.ct
683 name, description, t_description = self.parse_described_name()
685 if self.peek_kw("extends"):
686 self.match_kw("extends")
687 t_extends = self.ct
688 root_record = self.parse_qualified_name(self.default_scope,
689 ast.Record_Type)
690 root_record.set_ast_link(t_extends)
691 root_record.set_ast_link(self.ct)
692 else:
693 root_record = None
695 if self.lint_mode and \
696 root_record and root_record.is_final and \
697 not is_final:
698 self.mh.check(name.location,
699 "consider clarifying that this record is final",
700 "clarify_final",
701 ("Parent record %s is final, making this record\n"
702 "also final. Marking it explicitly as final\n"
703 "clarifies this to casual readers." %
704 root_record.fully_qualified_name()))
706 record = ast.Record_Type(name = name.value,
707 description = description,
708 location = name.location,
709 package = self.cu.package,
710 n_parent = root_record,
711 is_abstract = is_abstract)
712 self.cu.package.symbols.register(self.mh, record)
713 if is_abstract:
714 record.set_ast_link(t_abstract)
715 if is_final:
716 record.set_ast_link(t_final)
717 record.set_ast_link(t_type)
718 record.set_ast_link(name)
719 if t_description:
720 record.set_ast_link(t_description)
722 self.match("C_BRA")
723 record.set_ast_link(self.ct)
724 while not self.peek("C_KET"):
725 if self.peek_kw("freeze"):
726 self.match_kw("freeze")
727 t_freeze = self.ct
728 self.match("IDENTIFIER")
729 n_comp = record.components.lookup(self.mh,
730 self.ct,
731 ast.Composite_Component)
732 if record.is_frozen(n_comp):
733 n_value = record.get_freezing_expression(n_comp)
734 self.mh.error(
735 self.ct.location,
736 "duplicate freezing of %s, previously frozen at %s" %
737 (n_comp.name,
738 self.mh.cross_file_reference(n_value.location)))
739 n_comp.set_ast_link(t_freeze)
740 n_comp.set_ast_link(self.ct)
741 self.match("ASSIGN")
742 n_comp.set_ast_link(self.ct)
743 n_value = self.parse_value(n_comp.n_typ)
744 n_value.set_ast_link(self.ct)
746 record.frozen[n_comp.name] = n_value
748 else:
749 n_comp = self.parse_record_component(record)
750 if record.is_final:
751 self.mh.error(n_comp.location,
752 "cannot declare new components in"
753 " final record type")
754 else:
755 record.components.register(self.mh, n_comp)
757 self.match("C_KET")
758 record.set_ast_link(self.ct)
760 # Finally mark record final if applicable
761 if is_final:
762 record.is_final = True
764 return record
766 def parse_expression(self, scope):
767 # lobster-trace: LRM.Expression
768 assert isinstance(scope, ast.Scope)
770 n_lhs = self.parse_relation(scope)
772 if self.peek_kw("and"):
773 while self.peek_kw("and"):
774 self.match_kw("and")
775 t_op = self.ct
776 a_op = ast.Binary_Operator.LOGICAL_AND
777 t_op.ast_link = a_op
778 n_rhs = self.parse_relation(scope)
779 n_lhs = ast.Binary_Expression(
780 mh = self.mh,
781 location = t_op.location,
782 typ = self.builtin_bool,
783 operator = a_op,
784 n_lhs = n_lhs,
785 n_rhs = n_rhs)
787 elif self.peek_kw("or"):
788 while self.peek_kw("or"):
789 self.match_kw("or")
790 t_op = self.ct
791 a_op = ast.Binary_Operator.LOGICAL_OR
792 t_op.ast_link = a_op
793 n_rhs = self.parse_relation(scope)
794 n_lhs = ast.Binary_Expression(
795 mh = self.mh,
796 location = t_op.location,
797 typ = self.builtin_bool,
798 operator = a_op,
799 n_lhs = n_lhs,
800 n_rhs = n_rhs)
802 elif self.peek_kw("xor"):
803 self.match_kw("xor")
804 t_op = self.ct
805 a_op = ast.Binary_Operator.LOGICAL_XOR
806 t_op.ast_link = a_op
807 n_rhs = self.parse_relation(scope)
808 n_lhs = ast.Binary_Expression(
809 mh = self.mh,
810 location = t_op.location,
811 typ = self.builtin_bool,
812 operator = a_op,
813 n_lhs = n_lhs,
814 n_rhs = n_rhs)
816 elif self.peek_kw("implies"):
817 self.match_kw("implies")
818 t_op = self.ct
819 a_op = ast.Binary_Operator.LOGICAL_IMPLIES
820 t_op.ast_link = a_op
821 n_rhs = self.parse_relation(scope)
822 n_lhs = ast.Binary_Expression(
823 mh = self.mh,
824 location = t_op.location,
825 typ = self.builtin_bool,
826 operator = a_op,
827 n_lhs = n_lhs,
828 n_rhs = n_rhs)
830 return n_lhs
832 def parse_relation(self, scope):
833 # lobster-trace: LRM.Relation
834 # lobster-trace: LRM.Operators
835 assert isinstance(scope, ast.Scope)
836 relop_mapping = {"==" : ast.Binary_Operator.COMP_EQ,
837 "!=" : ast.Binary_Operator.COMP_NEQ,
838 "<" : ast.Binary_Operator.COMP_LT,
839 "<=" : ast.Binary_Operator.COMP_LEQ,
840 ">" : ast.Binary_Operator.COMP_GT,
841 ">=" : ast.Binary_Operator.COMP_GEQ}
842 assert set(relop_mapping) == set(Parser.COMPARISON_OPERATOR)
844 n_lhs = self.parse_simple_expression(scope)
846 if self.peek("OPERATOR") and \
847 self.nt.value in Parser.COMPARISON_OPERATOR:
848 self.match("OPERATOR")
849 t_op = self.ct
850 a_op = relop_mapping[t_op.value]
851 t_op.ast_link = a_op
852 n_rhs = self.parse_simple_expression(scope)
853 return ast.Binary_Expression(
854 mh = self.mh,
855 location = t_op.location,
856 typ = self.builtin_bool,
857 operator = a_op,
858 n_lhs = n_lhs,
859 n_rhs = n_rhs)
861 elif self.peek_kw("not") or self.peek_kw("in"):
862 if self.peek_kw("not"):
863 self.match_kw("not")
864 t_not = self.ct
865 else:
866 t_not = None
868 self.match_kw("in")
869 t_in = self.ct
871 n_a = self.parse_simple_expression(scope)
872 t_n_a = self.ct
873 if self.peek("RANGE"):
874 self.match("RANGE")
875 t_range = self.ct
876 n_b = self.parse_simple_expression(scope)
877 n_b.set_ast_link(self.ct)
878 n_a.set_ast_link(t_n_a)
879 rv = ast.Range_Test(
880 mh = self.mh,
881 location = t_in.location,
882 typ = self.builtin_bool,
883 n_lhs = n_lhs,
884 n_lower = n_a,
885 n_upper = n_b)
886 rv.set_ast_link(t_range)
887 rv.set_ast_link(t_in)
889 elif isinstance(n_a.typ, ast.Builtin_String):
890 rv = ast.Binary_Expression(
891 mh = self.mh,
892 location = t_in.location,
893 typ = self.builtin_bool,
894 operator = ast.Binary_Operator.STRING_CONTAINS,
895 n_lhs = n_lhs,
896 n_rhs = n_a)
897 rv.set_ast_link(t_in)
899 elif isinstance(n_a.typ, ast.Array_Type): 899 ↛ 911line 899 didn't jump to line 911 because the condition on line 899 was always true
900 a_op = ast.Binary_Operator.ARRAY_CONTAINS
901 t_in.ast_link = a_op
902 rv = ast.Binary_Expression(
903 mh = self.mh,
904 location = t_in.location,
905 typ = self.builtin_bool,
906 operator = a_op,
907 n_lhs = n_lhs,
908 n_rhs = n_a)
910 else:
911 self.mh.error(
912 n_a.location,
913 "membership test only defined for Strings and Arrays,"
914 " not for %s" % n_a.typ.name)
916 if t_not is not None:
917 a_unary_op = ast.Unary_Operator.LOGICAL_NOT
918 t_not.ast_link = a_unary_op
919 rv = ast.Unary_Expression(
920 mh = self.mh,
921 location = t_not.location,
922 typ = self.builtin_bool,
923 operator = a_unary_op,
924 n_operand = rv)
926 return rv
928 else:
929 return n_lhs
931 def parse_simple_expression(self, scope):
932 # lobster-trace: LRM.Simple_Expression
933 # lobster-trace: LRM.Operators
934 # lobster-trace: LRM.Unary_Minus_Parsing
935 assert isinstance(scope, ast.Scope)
936 un_add_map = {"+" : ast.Unary_Operator.PLUS,
937 "-" : ast.Unary_Operator.MINUS}
938 bin_add_map = {"+" : ast.Binary_Operator.PLUS,
939 "-" : ast.Binary_Operator.MINUS}
940 assert set(un_add_map) == set(Parser.ADDING_OPERATOR)
941 assert set(bin_add_map) == set(Parser.ADDING_OPERATOR)
943 if self.peek("OPERATOR") and \
944 self.nt.value in Parser.ADDING_OPERATOR:
945 self.match("OPERATOR")
946 t_unary = self.ct
947 a_unary = un_add_map[t_unary.value]
948 t_unary.ast_link = a_unary
949 has_explicit_brackets = self.peek("BRA")
950 else:
951 t_unary = None
953 n_lhs = self.parse_term(scope)
954 if t_unary:
955 # pylint: disable=possibly-used-before-assignment
956 if self.lint_mode and \
957 isinstance(n_lhs, ast.Binary_Expression) and \
958 not has_explicit_brackets:
959 self.mh.check(t_unary.location,
960 "expression means -(%s), place explicit "
961 "brackets to clarify intent" %
962 n_lhs.to_string(),
963 "unary_minus_precedence")
965 n_lhs = ast.Unary_Expression(
966 mh = self.mh,
967 location = t_unary.location,
968 typ = n_lhs.typ,
969 operator = a_unary,
970 n_operand = n_lhs)
972 if isinstance(n_lhs.typ, ast.Builtin_String):
973 rtyp = self.builtin_str
974 else:
975 rtyp = n_lhs.typ
977 while self.peek("OPERATOR") and \
978 self.nt.value in Parser.ADDING_OPERATOR:
979 self.match("OPERATOR")
980 t_op = self.ct
981 a_op = bin_add_map[t_op.value]
982 t_op.ast_link = a_op
983 n_rhs = self.parse_term(scope)
984 n_lhs = ast.Binary_Expression(
985 mh = self.mh,
986 location = t_op.location,
987 typ = rtyp,
988 operator = a_op,
989 n_lhs = n_lhs,
990 n_rhs = n_rhs)
992 return n_lhs
994 def parse_term(self, scope):
995 # lobster-trace: LRM.Term
996 # lobster-trace: LRM.Operators
997 assert isinstance(scope, ast.Scope)
998 mul_map = {"*" : ast.Binary_Operator.TIMES,
999 "/" : ast.Binary_Operator.DIVIDE,
1000 "%" : ast.Binary_Operator.REMAINDER}
1001 assert set(mul_map) == set(Parser.MULTIPLYING_OPERATOR)
1003 n_lhs = self.parse_factor(scope)
1004 while self.peek("OPERATOR") and \
1005 self.nt.value in Parser.MULTIPLYING_OPERATOR:
1006 self.match("OPERATOR")
1007 t_op = self.ct
1008 a_op = mul_map[t_op.value]
1009 t_op.ast_link = a_op
1010 n_rhs = self.parse_factor(scope)
1011 n_lhs = ast.Binary_Expression(
1012 mh = self.mh,
1013 location = t_op.location,
1014 typ = n_lhs.typ,
1015 operator = a_op,
1016 n_lhs = n_lhs,
1017 n_rhs = n_rhs)
1019 return n_lhs
1021 def parse_factor(self, scope):
1022 # lobster-trace: LRM.Factor
1023 assert isinstance(scope, ast.Scope)
1025 if self.peek_kw("not"):
1026 self.match_kw("not")
1027 t_op = self.ct
1028 n_operand = self.parse_primary(scope)
1029 a_not = ast.Unary_Operator.LOGICAL_NOT
1030 t_op.ast_link = a_not
1031 return ast.Unary_Expression(
1032 mh = self.mh,
1033 location = t_op.location,
1034 typ = self.builtin_bool,
1035 operator = a_not,
1036 n_operand = n_operand)
1038 elif self.peek_kw("abs"):
1039 self.match_kw("abs")
1040 t_op = self.ct
1041 n_operand = self.parse_primary(scope)
1042 a_abs = ast.Unary_Operator.ABSOLUTE_VALUE
1043 t_op.ast_link = a_abs
1044 return ast.Unary_Expression(
1045 mh = self.mh,
1046 location = t_op.location,
1047 typ = n_operand.typ,
1048 operator = a_abs,
1049 n_operand = n_operand)
1051 else:
1052 n_lhs = self.parse_primary(scope)
1053 if self.peek("OPERATOR") and self.nt.value == "**":
1054 self.match("OPERATOR")
1055 t_op = self.ct
1056 n_rhs = self.parse_primary(scope)
1057 rhs_value = n_rhs.evaluate(self.mh, None, None)
1058 a_binary = ast.Binary_Operator.POWER
1059 t_op.ast_link = a_binary
1060 n_lhs = ast.Binary_Expression(
1061 mh = self.mh,
1062 location = t_op.location,
1063 typ = n_lhs.typ,
1064 operator = a_binary,
1065 n_lhs = n_lhs,
1066 n_rhs = n_rhs)
1067 if rhs_value.value < 0: 1067 ↛ 1068line 1067 didn't jump to line 1068 because the condition on line 1067 was never true
1068 self.mh.error(n_rhs.location,
1069 "exponent must not be negative")
1070 return n_lhs
1072 def parse_primary(self, scope):
1073 # lobster-trace: LRM.Primary
1074 assert isinstance(scope, ast.Scope)
1076 if self.peek("INTEGER"):
1077 # lobster-trace: LRM.Integer_Values
1078 self.match("INTEGER")
1079 int_lit = ast.Integer_Literal(self.ct, self.builtin_int)
1080 int_lit.set_ast_link(self.ct)
1081 return int_lit
1083 elif self.peek("DECIMAL"):
1084 # lobster-trace: LRM.Decimal_Values
1085 self.match("DECIMAL")
1086 dec_lit = ast.Decimal_Literal(self.ct, self.builtin_decimal)
1087 dec_lit.set_ast_link(self.ct)
1088 return dec_lit
1090 elif self.peek("STRING"):
1091 # lobster-trace: LRM.String_Values
1092 self.match("STRING")
1093 string_lit = ast.String_Literal(self.ct, self.builtin_str)
1094 string_lit.set_ast_link(self.ct)
1095 return string_lit
1097 elif self.peek_kw("true") or self.peek_kw("false"):
1098 # lobster-trace: LRM.Boolean_Values
1099 self.match("KEYWORD")
1100 bool_lit = ast.Boolean_Literal(self.ct, self.builtin_bool)
1101 bool_lit.set_ast_link(self.ct)
1102 return bool_lit
1104 elif self.peek_kw("null"):
1105 self.match_kw("null")
1106 null_lit = ast.Null_Literal(self.ct)
1107 null_lit.set_ast_link(self.ct)
1108 return null_lit
1110 elif self.peek("BRA"):
1111 self.match("BRA")
1112 t_bra = self.ct
1113 if self.peek_kw("forall") or self.peek_kw("exists"):
1114 rv = self.parse_quantified_expression(scope)
1115 elif self.peek_kw("if"):
1116 rv = self.parse_conditional_expression(scope)
1117 else:
1118 rv = self.parse_expression(scope)
1119 rv.set_ast_link(t_bra)
1120 self.match("KET")
1121 rv.set_ast_link(self.ct)
1122 return rv
1124 else:
1125 return self.parse_name(scope)
1127 def parse_quantified_expression(self, scope):
1128 # lobster-trace: LRM.Quantified_Expression
1129 assert isinstance(scope, ast.Scope)
1131 if self.peek_kw("forall"):
1132 self.match_kw("forall")
1133 t_quantified = self.ct
1134 universal = True
1135 else:
1136 self.match_kw("exists")
1137 t_quantified = self.ct
1138 universal = False
1139 loc = self.ct.location
1140 self.match("IDENTIFIER")
1141 t_qv = self.ct
1142 if scope.contains(t_qv.value):
1143 # lobster-trace: LRM.Quantification_Naming_Scope
1144 pdef = scope.lookup(self.mh, t_qv)
1145 self.mh.error(t_qv.location,
1146 "shadows %s %s from %s" %
1147 (pdef.__class__.__name__,
1148 pdef.name,
1149 self.mh.cross_file_reference(pdef.location)))
1150 self.match_kw("in")
1151 t_in = self.ct
1152 self.match("IDENTIFIER")
1153 field = scope.lookup(self.mh, self.ct, ast.Composite_Component)
1154 n_source = ast.Name_Reference(self.ct.location,
1155 field)
1156 n_source.set_ast_link(self.ct)
1157 if not isinstance(field.n_typ, ast.Array_Type):
1158 # lobster-trace: LRM.Quantification_Object
1159 self.mh.error(self.ct.location,
1160 "you can only quantify over arrays")
1161 n_var = ast.Quantified_Variable(t_qv.value,
1162 t_qv.location,
1163 field.n_typ.element_type)
1164 n_var.set_ast_link(t_qv)
1165 self.match("ARROW")
1166 t_arrow = self.ct
1168 new_table = ast.Symbol_Table()
1169 new_table.register(self.mh, n_var)
1170 scope.push(new_table)
1171 n_expr = self.parse_expression(scope)
1172 scope.pop()
1174 quantified_expression = ast.Quantified_Expression(
1175 mh = self.mh,
1176 location = loc,
1177 typ = self.builtin_bool,
1178 universal = universal,
1179 n_variable = n_var,
1180 n_source = n_source,
1181 n_expr = n_expr)
1183 quantified_expression.set_ast_link(t_quantified)
1184 quantified_expression.set_ast_link(t_in)
1185 quantified_expression.set_ast_link(t_arrow)
1187 return quantified_expression
1189 def parse_conditional_expression(self, scope):
1190 # lobster-trace: LRM.Conditional_Expression
1191 # lobster-trace: LRM.Restricted_Null
1192 assert isinstance(scope, ast.Scope)
1194 self.match_kw("if")
1195 t_if = self.ct
1196 if_cond = self.parse_expression(scope)
1197 self.match_kw("then")
1198 t_then = self.ct
1199 if_expr = self.parse_expression(scope)
1200 if if_expr.typ is None:
1201 self.mh.error(if_expr.location,
1202 "null is not permitted here")
1203 if_action = ast.Action(self.mh, t_if, if_cond, if_expr)
1205 rv = ast.Conditional_Expression(location = t_if.location,
1206 if_action = if_action)
1207 if_action.set_ast_link(t_if)
1208 if_action.set_ast_link(t_then)
1210 while self.peek_kw("elsif"):
1211 self.match_kw("elsif")
1212 t_elsif = self.ct
1213 elsif_cond = self.parse_expression(scope)
1214 self.match_kw("then")
1215 t_then = self.ct
1216 elsif_expr = self.parse_expression(scope)
1217 elsif_action = ast.Action(self.mh, t_elsif, elsif_cond, elsif_expr)
1218 elsif_action.set_ast_link(t_elsif)
1219 elsif_action.set_ast_link(t_then)
1220 rv.add_elsif(self.mh, elsif_action)
1222 self.match_kw("else")
1223 rv.set_ast_link(self.ct)
1224 else_expr = self.parse_expression(scope)
1225 rv.set_else_part(self.mh, else_expr)
1227 return rv
1229 def parse_builtin(self, scope, n_name, t_name):
1230 # lobster-trace: LRM.Builtin_Functions
1231 # lobster-trace: LRM.Builtin_Type_Conversion_Functions
1232 assert isinstance(scope, ast.Scope)
1233 assert isinstance(n_name, (ast.Builtin_Function,
1234 ast.Builtin_Numeric_Type))
1235 assert isinstance(t_name, Token)
1237 # Parse the arguments.
1238 parameters = []
1239 n_name.set_ast_link(self.ct)
1240 self.match("BRA")
1241 n_name.set_ast_link(self.ct)
1242 while not self.peek("KET"): 1242 ↛ 1253line 1242 didn't jump to line 1253 because the condition on line 1242 was always true
1243 exp = self.parse_expression(scope)
1244 if not self.ct.ast_link: 1244 ↛ 1245line 1244 didn't jump to line 1245 because the condition on line 1244 was never true
1245 exp.set_ast_link(self.ct)
1246 parameters.append(exp)
1248 if self.peek("COMMA"): 1248 ↛ 1252line 1248 didn't jump to line 1252 because the condition on line 1248 was always true
1249 self.match("COMMA")
1250 n_name.set_ast_link(self.ct)
1251 else:
1252 break
1253 self.match("KET")
1254 n_name.set_ast_link(self.ct)
1256 # Enforce arity
1257 if isinstance(n_name, ast.Builtin_Function):
1258 required_arity = n_name.arity
1259 precise = not n_name.arity_at_least
1260 else:
1261 required_arity = 1
1262 precise = True
1264 if precise:
1265 if required_arity != len(parameters):
1266 self.mh.error(t_name.location,
1267 "function requires %u parameters" %
1268 n_name.arity)
1269 else:
1270 if required_arity > len(parameters): 1270 ↛ 1271line 1270 didn't jump to line 1271 because the condition on line 1270 was never true
1271 self.mh.error(t_name.location,
1272 "function requires at least %u parameters" %
1273 n_name.arity)
1275 # Enforce types
1276 if n_name.name == "len":
1277 if isinstance(parameters[0].typ, ast.Builtin_String):
1278 return ast.Unary_Expression(
1279 mh = self.mh,
1280 location = t_name.location,
1281 typ = self.builtin_int,
1282 operator = ast.Unary_Operator.STRING_LENGTH,
1283 n_operand = parameters[0])
1284 else:
1285 return ast.Unary_Expression(
1286 mh = self.mh,
1287 location = t_name.location,
1288 typ = self.builtin_int,
1289 operator = ast.Unary_Operator.ARRAY_LENGTH,
1290 n_operand = parameters[0])
1292 elif n_name.name in ("startswith",
1293 "endswith"):
1294 return ast.Binary_Expression(
1295 mh = self.mh,
1296 location = t_name.location,
1297 typ = self.builtin_bool,
1298 operator = (ast.Binary_Operator.STRING_STARTSWITH
1299 if "startswith" in n_name.name
1300 else ast.Binary_Operator.STRING_ENDSWITH),
1301 n_lhs = parameters[0],
1302 n_rhs = parameters[1])
1304 elif n_name.name == "matches":
1305 parameters[1].ensure_type(self.mh, ast.Builtin_String)
1306 try:
1307 # lobster-trace: LRM.Static_Regular_Expression
1308 # scope is None on purpose to enforce static context
1309 value = parameters[1].evaluate(self.mh, None, None)
1310 assert isinstance(value.typ, ast.Builtin_String)
1311 re.compile(value.value)
1312 except re.error as err:
1313 self.mh.error(value.location,
1314 str(err))
1315 return ast.Binary_Expression(
1316 mh = self.mh,
1317 location = t_name.location,
1318 typ = self.builtin_bool,
1319 operator = ast.Binary_Operator.STRING_REGEX,
1320 n_lhs = parameters[0],
1321 n_rhs = parameters[1])
1323 elif n_name.name == "oneof":
1324 return ast.OneOf_Expression(
1325 mh = self.mh,
1326 location = t_name.location,
1327 typ = self.builtin_bool,
1328 choices = parameters)
1330 elif isinstance(n_name, ast.Builtin_Numeric_Type):
1331 parameters[0].ensure_type(self.mh, ast.Builtin_Numeric_Type)
1332 if isinstance(n_name, ast.Builtin_Integer):
1333 return ast.Unary_Expression(
1334 mh = self.mh,
1335 location = t_name.location,
1336 typ = self.builtin_int,
1337 operator = ast.Unary_Operator.CONVERSION_TO_INT,
1338 n_operand = parameters[0])
1339 elif isinstance(n_name, ast.Builtin_Decimal):
1340 return ast.Unary_Expression(
1341 mh = self.mh,
1342 location = t_name.location,
1343 typ = self.builtin_decimal,
1344 operator = ast.Unary_Operator.CONVERSION_TO_DECIMAL,
1345 n_operand = parameters[0])
1346 else:
1347 self.mh.ice_loc(t_name.location,
1348 "unexpected type conversion")
1350 else:
1351 self.mh.ice_loc(t_name.location,
1352 "unexpected builtin")
1354 def parse_name(self, scope):
1355 # lobster-trace: LRM.Names
1357 # This is a bit more complex. The grammar is:
1358 #
1359 # qualified_name ::= [ IDENTIFIER_package_name '.' ] IDENTIFIER_name
1360 #
1361 # name ::= qualified_name
1362 # | name '.' IDENTIFIER
1363 # | name '[' expression ']'
1364 # | name '(' parameter_list ')'
1365 #
1366 # parameter_list ::= expression { ',' expression }
1368 assert isinstance(scope, ast.Scope)
1370 # All names start with a (qualified) identifier. We parse that
1371 # first. There is a special complication for functions, as
1372 # builtin functions (e.g. len) can shadow record
1373 # components. However as functions cannot be stored in
1374 # components the true grammar for function calls is always
1375 # IDENTIFIER '('; so we can slightly special case this.
1377 # lobster-trace: LRM.Builtin_Functions
1378 # lobster-trace: LRM.Builtin_Type_Conversion_Functions
1379 self.match("IDENTIFIER")
1380 if self.peek("BRA"):
1381 # If we follow our name with brackets
1382 # immediately, we have a builtin function call.
1383 n_name = self.stab.lookup(self.mh,
1384 self.ct)
1385 if not isinstance(n_name, (ast.Builtin_Function, 1385 ↛ 1387line 1385 didn't jump to line 1387 because the condition on line 1385 was never true
1386 ast.Builtin_Numeric_Type)):
1387 self.mh.error(self.ct.location,
1388 "not a valid builtin function "
1389 "or numeric type")
1390 else:
1391 n_name = self.parse_qualified_name(scope, match_ident=False)
1393 # Enum literals are a bit different, so we deal with them
1394 # first.
1395 if isinstance(n_name, ast.Enumeration_Type):
1396 n_name.set_ast_link(self.ct)
1397 self.match("DOT")
1398 n_name.set_ast_link(self.ct)
1399 self.match("IDENTIFIER")
1400 lit = n_name.literals.lookup(self.mh,
1401 self.ct,
1402 ast.Enumeration_Literal_Spec)
1403 enum_lit = ast.Enumeration_Literal(location = self.ct.location,
1404 literal = lit)
1405 enum_lit.set_ast_link(self.ct)
1406 return enum_lit
1408 # Anything that remains is either a function call or an actual
1409 # name. Let's just enforce this for sanity.
1410 if not isinstance(n_name, (ast.Builtin_Function, 1410 ↛ 1415line 1410 didn't jump to line 1415 because the condition on line 1410 was never true
1411 ast.Builtin_Numeric_Type,
1412 ast.Composite_Component,
1413 ast.Quantified_Variable,
1414 )):
1415 self.mh.error(self.ct.location,
1416 "%s %s is not a valid name" %
1417 (n_name.__class__.__name__,
1418 n_name.name))
1420 # Right now function calls and type conversions must be
1421 # top-level, so let's get these out of the way as well.
1422 if isinstance(n_name, (ast.Builtin_Function,
1423 ast.Builtin_Numeric_Type)):
1424 # lobster-trace: LRM.Builtin_Functions
1425 # lobster-trace: LRM.Builtin_Type_Conversion_Functions
1426 return self.parse_builtin(scope, n_name, self.ct)
1428 assert isinstance(n_name, (ast.Composite_Component,
1429 ast.Quantified_Variable))
1431 # We now process the potentially recursive part:
1432 # | name '.' IDENTIFIER
1433 # | name '[' expression ']'
1434 n_name = ast.Name_Reference(location = self.ct.location,
1435 entity = n_name)
1436 n_name.set_ast_link(self.ct)
1437 while self.peek("DOT") or self.peek("S_BRA"):
1438 if self.peek("DOT"):
1439 if not isinstance(n_name.typ, (ast.Tuple_Type, 1439 ↛ 1443line 1439 didn't jump to line 1443 because the condition on line 1439 was never true
1440 ast.Record_Type,
1441 ast.Union_Type)):
1442 # lobster-trace: LRM.Valid_Index_Prefixes
1443 self.mh.error(n_name.location,
1444 "expression '%s' has type %s, "
1445 "which is not a tuple, record, or union" %
1446 (n_name.to_string(),
1447 n_name.typ.name))
1449 self.match("DOT")
1450 t_dot = self.ct
1451 self.match("IDENTIFIER")
1452 t_field = self.ct
1454 is_union_access = isinstance(n_name.typ,
1455 ast.Union_Type)
1456 is_universal = True
1458 if is_union_access:
1459 # lobster-trace: LRM.Union_Type_Field_Access
1460 # lobster-trace: LRM.Union_Type_Field_Type_Conflict
1461 # lobster-trace: LRM.Union_Type_Field_Access_Validity
1462 field_name = t_field.value
1463 field_map = n_name.typ.get_field_map()
1464 if field_name not in field_map:
1465 self.mh.error(
1466 t_field.location,
1467 "field %s does not exist in any member"
1468 " of union type %s" %
1469 (field_name, n_name.typ.name))
1470 info = field_map[field_name]
1471 if info["n_typ"] is None:
1472 self.mh.error(
1473 t_field.location,
1474 "field %s has conflicting types in"
1475 " members of union type %s" %
1476 (field_name, n_name.typ.name))
1477 is_universal = info["count"] == info["total"]
1478 # lobster-trace: LRM.Union_Type_Partial_Field_Access
1479 if self.lint_mode and not is_universal:
1480 self.mh.check(
1481 t_field.location,
1482 "field %s exists only in %u of %u"
1483 " members of union type %s;"
1484 " accessing it on other members"
1485 " returns null" %
1486 (field_name,
1487 info["count"],
1488 info["total"],
1489 n_name.typ.name),
1490 "union_partial_field_access")
1491 n_field = info["component"]
1492 else:
1493 n_field = n_name.typ.components.lookup(
1494 self.mh,
1495 t_field,
1496 ast.Composite_Component)
1498 n_field.set_ast_link(t_field)
1499 n_name = ast.Field_Access_Expression(
1500 mh = self.mh,
1501 location = t_field.location,
1502 n_prefix = n_name,
1503 n_field = n_field,
1504 is_union_access = is_union_access,
1505 is_universal = is_universal)
1506 n_name.set_ast_link(t_dot)
1508 elif self.peek("S_BRA"): 1508 ↛ 1437line 1508 didn't jump to line 1437 because the condition on line 1508 was always true
1509 if not isinstance(n_name.typ, ast.Array_Type):
1510 self.mh.error(n_name.location,
1511 "expression '%s' has type %s, "
1512 "which is not an array" %
1513 (n_name.to_string(),
1514 n_name.typ.name))
1516 self.match("S_BRA")
1517 t_bracket = self.ct
1518 n_index = self.parse_expression(scope)
1519 self.match("S_KET")
1520 a_binary = ast.Binary_Operator.INDEX
1521 t_bracket.ast_link = a_binary
1522 self.ct.ast_link = a_binary
1524 n_name = ast.Binary_Expression(
1525 mh = self.mh,
1526 location = t_bracket.location,
1527 typ = n_name.typ.element_type,
1528 operator = a_binary,
1529 n_lhs = n_name,
1530 n_rhs = n_index)
1532 return n_name
1534 def parse_check_block(self):
1535 # lobster-trace: LRM.Check_Block
1536 t_severity = None
1537 self.match_kw("checks")
1538 t_checks = self.ct
1539 self.match("IDENTIFIER")
1540 # lobster-trace: LRM.Applicable_Types
1541 # lobster-trace: LRM.Applicable_Components
1542 n_ctype = self.cu.package.symbols.lookup(self.mh,
1543 self.ct,
1544 ast.Composite_Type)
1545 n_check_block = ast.Check_Block(location = self.ct.location,
1546 n_typ = n_ctype)
1547 n_check_block.set_ast_link(t_checks)
1548 n_ctype.set_ast_link(self.ct)
1549 scope = ast.Scope()
1550 scope.push(self.stab)
1551 scope.push(self.cu.package.symbols)
1552 scope.push(n_ctype.components)
1553 self.match("C_BRA")
1554 n_check_block.set_ast_link(self.ct)
1555 while not self.peek("C_KET"):
1556 c_expr = self.parse_expression(scope)
1557 if not isinstance(c_expr.typ, ast.Builtin_Boolean): 1557 ↛ 1558line 1557 didn't jump to line 1558 because the condition on line 1557 was never true
1558 self.mh.error(c_expr.location,
1559 "check expression must be Boolean")
1561 self.match("COMMA")
1562 t_first_comma = self.ct
1563 if self.peek("KEYWORD"):
1564 self.match("KEYWORD")
1565 t_severity = self.ct
1566 if self.ct.value not in ("warning", "error", "fatal"): 1566 ↛ 1567line 1566 didn't jump to line 1567 because the condition on line 1566 was never true
1567 self.mh.error(self.ct.location,
1568 "expected warning|error|fatal")
1569 c_sev = self.ct.value
1570 else:
1571 c_sev = "error"
1573 self.match("STRING")
1574 if "\n" in self.ct.value:
1575 # lobster-trace: LRM.No_Newlines_In_Message
1576 self.mh.error(self.ct.location,
1577 "error message must not contain a newline",
1578 fatal = False)
1579 t_msg = self.ct
1581 has_extrainfo = False
1582 has_anchor = False
1583 if self.peek("COMMA"):
1584 self.match("COMMA")
1585 t_second_comma = self.ct
1586 if self.peek("IDENTIFIER"):
1587 has_anchor = True
1588 elif self.peek("STRING"): 1588 ↛ 1591line 1588 didn't jump to line 1591 because the condition on line 1588 was always true
1589 has_extrainfo = True
1590 else:
1591 self.mh.error(self.nt.location,
1592 "expected either a details string or"
1593 " identifier to anchor the check message")
1595 if has_extrainfo:
1596 self.match("STRING")
1597 t_extrainfo = self.ct
1598 c_extrainfo = self.ct.value
1600 if self.peek("COMMA"): 1600 ↛ 1601line 1600 didn't jump to line 1601 because the condition on line 1600 was never true
1601 self.match("COMMA")
1602 t_third_comma = self.ct
1603 has_anchor = True
1605 else:
1606 c_extrainfo = None
1608 if has_anchor:
1609 self.match("IDENTIFIER")
1610 t_anchor = self.ct
1611 c_anchor = n_ctype.components.lookup(self.mh,
1612 self.ct,
1613 ast.Composite_Component)
1614 else:
1615 c_anchor = None
1617 n_check = ast.Check(n_type = n_ctype,
1618 n_expr = c_expr,
1619 n_anchor = c_anchor,
1620 severity = c_sev,
1621 t_message = t_msg,
1622 extrainfo = c_extrainfo)
1624 # pylint: disable=possibly-used-before-assignment
1625 # pylint: disable=used-before-assignment
1627 n_check.set_ast_link(t_first_comma)
1628 if t_severity:
1629 n_check.set_ast_link(t_severity)
1630 n_check.set_ast_link(t_msg)
1631 if c_extrainfo or c_anchor:
1632 n_check.set_ast_link(t_second_comma)
1633 if c_extrainfo:
1634 n_check.set_ast_link(t_extrainfo)
1635 if c_anchor:
1636 c_anchor.set_ast_link(t_anchor)
1637 if c_anchor and c_extrainfo: 1637 ↛ 1638line 1637 didn't jump to line 1638 because the condition on line 1637 was never true
1638 n_check.set_ast_link(t_third_comma)
1640 n_ctype.add_check(n_check)
1641 n_check_block.add_check(n_check)
1643 assert scope.size() == 3
1645 self.match("C_KET")
1646 n_check_block.set_ast_link(self.ct)
1648 return n_check_block
1650 def parse_section_declaration(self):
1651 # lobster-trace: LRM.Section_Declaration
1652 self.match_kw("section")
1653 t_section = self.ct
1654 self.match("STRING")
1655 sec = ast.Section(name = self.ct.value,
1656 location = self.ct.location,
1657 parent = self.section[-1] if self.section else None)
1658 sec.set_ast_link(self.ct)
1659 sec.set_ast_link(t_section)
1660 self.section.append(sec)
1661 self.match("C_BRA")
1662 sec.set_ast_link(self.ct)
1663 while not self.peek("C_KET"):
1664 self.parse_trlc_entry()
1665 self.match("C_KET")
1666 sec.set_ast_link(self.ct)
1667 self.section.pop()
1669 def parse_boolean(self):
1670 # lobster-trace: LRM.Boolean_Values
1671 self.match("KEYWORD")
1672 if self.ct.value in ("true", "false"): 1672 ↛ 1675line 1672 didn't jump to line 1675 because the condition on line 1672 was always true
1673 return ast.Boolean_Literal(self.ct, self.builtin_bool)
1674 else:
1675 self.mh.error(self.ct.location,
1676 "expected boolean literal (true or false)")
1678 def parse_value(self, typ):
1679 # lobster-trace: LRM.Tuple_Syntax_Correct_Form
1680 assert isinstance(typ, ast.Type)
1682 if isinstance(typ, ast.Builtin_Numeric_Type):
1683 # lobster-trace: LRM.Integer_Values
1684 # lobster-trace: LRM.Decimal_Values
1685 if self.peek("OPERATOR") and \
1686 self.nt.value in Parser.ADDING_OPERATOR:
1687 self.match("OPERATOR")
1688 t_op = self.ct
1689 e_op = (ast.Unary_Operator.PLUS
1690 if t_op.value == "+"
1691 else ast.Unary_Operator.MINUS)
1692 t_op.ast_link = e_op
1693 else:
1694 t_op = None
1696 if isinstance(typ, ast.Builtin_Decimal):
1697 self.match("DECIMAL")
1698 rv = ast.Decimal_Literal(self.ct, self.builtin_decimal)
1699 rv.set_ast_link(self.ct)
1700 elif isinstance(typ, ast.Builtin_Integer):
1701 self.match("INTEGER")
1702 rv = ast.Integer_Literal(self.ct, self.builtin_int)
1703 rv.set_ast_link(self.ct)
1704 else:
1705 assert False
1707 if t_op:
1708 rv = ast.Unary_Expression(mh = self.mh,
1709 location = t_op.location,
1710 typ = rv.typ,
1711 operator = e_op,
1712 n_operand = rv)
1714 return rv
1716 elif isinstance(typ, ast.Builtin_Markup_String):
1717 # lobster-trace: LRM.Markup_String_Values
1718 return self.parse_markup_string()
1720 elif isinstance(typ, ast.Builtin_String):
1721 # lobster-trace: LRM.String_Values
1722 self.match("STRING")
1723 rv = ast.String_Literal(self.ct, self.builtin_str)
1724 rv.set_ast_link(self.ct)
1725 return rv
1727 elif isinstance(typ, ast.Builtin_Boolean):
1728 rv = self.parse_boolean()
1729 rv.set_ast_link(self.ct)
1730 return rv
1732 elif isinstance(typ, ast.Array_Type):
1733 self.match("S_BRA")
1734 rv = ast.Array_Aggregate(self.ct.location,
1735 typ)
1736 rv.set_ast_link(self.ct)
1737 while not self.peek("S_KET"):
1738 array_elem = self.parse_value(typ.element_type)
1739 rv.append(array_elem)
1740 if self.peek("COMMA"):
1741 self.match("COMMA")
1742 rv.set_ast_link(self.ct)
1743 elif self.peek("S_KET") or self.nt is None: 1743 ↛ anywhereline 1743 didn't jump anywhere: it always raised an exception.
1744 break
1745 else:
1746 self.mh.error(self.ct.location,
1747 "comma separating array elements is "
1748 "missing",
1749 fatal = False)
1751 self.match("S_KET")
1752 rv.set_ast_link(self.ct)
1754 if len(rv.value) < typ.lower_bound:
1755 self.mh.error(self.ct.location,
1756 "this array requires at least %u elements "
1757 "(only %u provided)" %
1758 (typ.lower_bound,
1759 len(rv.value)),
1760 fatal=False)
1761 if typ.upper_bound and len(rv.value) > typ.upper_bound:
1762 self.mh.error(rv.value[typ.upper_bound].location,
1763 "this array requires at most %u elements "
1764 "(%u provided)" %
1765 (typ.upper_bound,
1766 len(rv.value)),
1767 fatal=False)
1769 return rv
1771 elif isinstance(typ, ast.Enumeration_Type):
1772 enum = self.parse_qualified_name(self.default_scope,
1773 ast.Enumeration_Type)
1774 enum.set_ast_link(self.ct)
1775 if enum != typ:
1776 self.mh.error(self.ct.location,
1777 "expected %s" % typ.name)
1778 self.match("DOT")
1779 enum.set_ast_link(self.ct)
1780 self.match("IDENTIFIER")
1781 lit = enum.literals.lookup(self.mh,
1782 self.ct,
1783 ast.Enumeration_Literal_Spec)
1784 return ast.Enumeration_Literal(self.ct.location,
1785 lit)
1787 elif isinstance(typ, (ast.Record_Type, ast.Union_Type)):
1788 self.match("IDENTIFIER")
1789 t_name = self.ct
1790 if self.peek("DOT"):
1791 self.match("DOT")
1792 t_dot = self.ct
1793 self.match("IDENTIFIER")
1794 the_pkg = self.stab.lookup(self.mh, t_name, ast.Package)
1795 the_pkg.set_ast_link(t_name)
1796 the_pkg.set_ast_link(t_dot)
1797 if not self.cu.is_visible(the_pkg): 1797 ↛ 1798line 1797 didn't jump to line 1798 because the condition on line 1797 was never true
1798 self.mh.error(self.ct.location,
1799 "package must be imported before use")
1800 t_name = self.ct
1801 else:
1802 the_pkg = self.cu.package
1804 rv = ast.Record_Reference(location = t_name.location,
1805 name = t_name.value,
1806 typ = typ,
1807 package = the_pkg)
1808 rv.set_ast_link(t_name)
1810 # We can do an early lookup if the target is known
1811 if the_pkg.symbols.contains(t_name.value):
1812 rv.resolve_references(self.mh)
1814 return rv
1816 elif isinstance(typ, ast.Tuple_Type) and typ.has_separators():
1817 # lobster-trace: LRM.Tuple_Separator_Form
1818 rv = ast.Tuple_Aggregate(self.nt.location, typ)
1820 next_is_optional = False
1821 for n_item in typ.iter_sequence():
1822 if isinstance(n_item, ast.Composite_Component):
1823 if next_is_optional and n_item.optional:
1824 break
1825 value = self.parse_value(n_item.n_typ)
1826 rv.assign(n_item.name, value)
1828 elif n_item.token.kind in ("AT", "COLON", "SEMICOLON"):
1829 if self.peek(n_item.token.kind):
1830 self.match(n_item.token.kind)
1831 n_item.set_ast_link(self.ct)
1832 else:
1833 next_is_optional = True
1835 elif n_item.token.kind == "IDENTIFIER":
1836 if self.peek("IDENTIFIER") and \
1837 self.nt.value == n_item.token.value:
1838 self.match("IDENTIFIER")
1839 n_item.set_ast_link(self.ct)
1840 else:
1841 next_is_optional = True
1843 else:
1844 assert False
1846 return rv
1848 elif isinstance(typ, ast.Tuple_Type) and not typ.has_separators():
1849 # lobster-trace: LRM.Tuple_Generic_Form
1850 self.match("BRA")
1851 rv = ast.Tuple_Aggregate(self.ct.location, typ)
1852 rv.set_ast_link(self.ct)
1854 first = True
1855 for n_field in typ.iter_sequence():
1856 if first:
1857 first = False
1858 else:
1859 self.match("COMMA")
1860 rv.set_ast_link(self.ct)
1861 rv.assign(n_field.name,
1862 self.parse_value(n_field.n_typ))
1864 self.match("KET")
1865 rv.set_ast_link(self.ct)
1866 return rv
1868 else:
1869 self.mh.ice_loc(self.ct.location,
1870 "logic error: unexpected type %s" %
1871 typ.__class__.__name__)
1873 def parse_markup_string(self):
1874 # lobster-trace: LRM.Markup_String_Values
1875 self.match("STRING")
1876 rv = ast.String_Literal(self.ct, self.builtin_mstr)
1877 mpar = Markup_Parser(self, rv)
1878 mpar.parse_all_references()
1879 return rv
1881 def parse_record_object_declaration(self):
1882 # lobster-trace: LRM.Section_Declaration
1883 # lobster-trace: LRM.Record_Object_Declaration
1884 # lobster-trace: LRM.Valid_Record_Types
1885 # lobster-trace: LRM.Valid_Components
1886 # lobster-trace: LRM.Valid_Enumeration_Literals
1887 # lobster-trace: LRM.Mandatory_Components
1888 # lobster-trace: LRM.Evaluation_Of_Checks
1889 # lobster-trace: LRM.Single_Value_Assignment
1891 r_typ = self.parse_qualified_name(self.default_scope,
1892 ast.Record_Type)
1893 r_typ.set_ast_link(self.ct)
1894 # lobster-trace: LRM.Abstract_Types
1895 if r_typ.is_abstract:
1896 self.mh.error(self.ct.location,
1897 "cannot declare object of abstract record type %s" %
1898 r_typ.name)
1900 self.match("IDENTIFIER")
1901 obj = ast.Record_Object(
1902 name = self.ct.value,
1903 location = self.ct.location,
1904 n_typ = r_typ,
1905 section = self.section.copy() if self.section else None,
1906 n_package = self.cu.package)
1907 self.cu.package.symbols.register(self.mh, obj)
1908 obj.set_ast_link(self.ct)
1910 self.match("C_BRA")
1911 obj.set_ast_link(self.ct)
1912 while not self.peek("C_KET"):
1913 self.match("IDENTIFIER")
1914 comp = r_typ.components.lookup(self.mh,
1915 self.ct,
1916 ast.Composite_Component)
1917 if obj.is_component_implicit_null(comp):
1918 self.mh.error(self.ct.location,
1919 "component '%s' already assigned at line %i" %
1920 (comp.name,
1921 obj.field[comp.name].location.line_no))
1922 comp.set_ast_link(self.ct)
1923 if r_typ.is_frozen(comp):
1924 self.mh.error(self.ct.location,
1925 "cannot overwrite frozen component %s" %
1926 comp.name)
1927 self.match("ASSIGN")
1928 comp.set_ast_link(self.ct)
1929 value = self.parse_value(comp.n_typ)
1930 if not self.ct.ast_link:
1931 value.set_ast_link(self.ct)
1932 obj.assign(comp, value)
1934 # Check that each non-optional component has been specified
1935 for comp in r_typ.all_components():
1936 if isinstance(obj.field[comp.name], ast.Implicit_Null):
1937 if r_typ.is_frozen(comp):
1938 obj.assign(comp, r_typ.get_freezing_expression(comp))
1939 elif not comp.optional:
1940 self.mh.error(
1941 obj.location,
1942 "required component %s (see %s) is not defined" %
1943 (comp.name,
1944 self.mh.cross_file_reference(comp.location)))
1946 self.match("C_KET")
1947 obj.set_ast_link(self.ct)
1949 return obj
1951 def parse_trlc_entry(self):
1952 # lobster-trace: LRM.TRLC_File
1953 if self.peek_kw("section"):
1954 self.parse_section_declaration()
1955 else:
1956 self.cu.add_item(self.parse_record_object_declaration())
1958 def parse_preamble(self, kind):
1959 assert kind in ("rsl", "trlc")
1960 # lobster-trace: LRM.Layout
1961 # lobster-trace: LRM.Preamble
1963 # First, parse package indication, declaring the package if
1964 # needed
1965 self.match_kw("package")
1966 t_pkg = self.ct
1967 self.match("IDENTIFIER")
1969 if kind == "rsl":
1970 declare_package = True
1971 else:
1972 # lobster-trace: LRM.Late_Package_Declarations
1973 declare_package = not self.stab.contains(self.ct.value)
1975 if declare_package:
1976 # lobster-trace: LRM.Package_Declaration
1977 pkg = ast.Package(name = self.ct.value,
1978 location = self.ct.location,
1979 builtin_stab = self.stab,
1980 declared_late = kind == "trlc")
1981 self.stab.register(self.mh, pkg)
1982 else:
1983 pkg = self.stab.lookup(self.mh, self.ct, ast.Package)
1985 pkg.set_ast_link(t_pkg)
1986 pkg.set_ast_link(self.ct)
1988 # lobster-trace: LRM.Current_Package
1989 self.cu.set_package(pkg)
1991 self.default_scope.push(self.cu.package.symbols)
1993 # Second, parse import list (but don't resolve names yet)
1994 # lobster-trace: LRM.Import_Visibility
1995 if kind != "check": 1995 ↛ exitline 1995 didn't return from function 'parse_preamble' because the condition on line 1995 was always true
1996 while self.peek_kw("import"):
1997 self.match_kw("import")
1998 pkg.set_ast_link(self.ct)
1999 self.match("IDENTIFIER")
2000 self.cu.add_import(self.mh, self.ct)
2002 def parse_rsl_file(self):
2003 # lobster-trace: LRM.RSL_File
2004 assert self.cu.package is not None
2006 ok = True
2007 while not self.peek_eof():
2008 try:
2009 if self.peek_kw("checks"):
2010 self.cu.add_item(self.parse_check_block())
2011 else:
2012 self.cu.add_item(self.parse_type_declaration())
2013 except TRLC_Error as err:
2014 if not self.error_recovery or err.kind == "lex error": 2014 ↛ 2015line 2014 didn't jump to line 2015 because the condition on line 2014 was never true
2015 raise
2017 ok = False
2019 # Recovery strategy is to scan until we get the next
2020 # relevant keyword
2021 self.skip_until_newline()
2022 while not self.peek_eof():
2023 if self.peek_kw("checks") or \
2024 self.peek_kw("type") or \
2025 self.peek_kw("abstract") or \
2026 self.peek_kw("final") or \
2027 self.peek_kw("tuple") or \
2028 self.peek_kw("enum"):
2029 break
2030 self.advance()
2031 self.skip_until_newline()
2033 self.match_eof()
2035 for tok in self.lexer.tokens:
2036 if tok.kind == "COMMENT":
2037 self.cu.package.set_ast_link(tok)
2039 return ok
2041 def parse_trlc_file(self):
2042 # lobster-trace: LRM.TRLC_File
2043 assert self.cu.package is not None
2045 ok = True
2047 while self.peek_kw("section") or self.peek("IDENTIFIER"):
2048 try:
2049 self.parse_trlc_entry()
2050 except TRLC_Error as err:
2051 if not self.error_recovery or err.kind == "lex error": 2051 ↛ 2052line 2051 didn't jump to line 2052 because the condition on line 2051 was never true
2052 raise
2054 ok = False
2056 # Recovery strategy is to keep going until we find an
2057 # identifier that is a package or type, or section, or
2058 # EOF
2059 self.skip_until_newline()
2060 while not self.peek_eof():
2061 if self.peek_kw("section"): 2061 ↛ 2062line 2061 didn't jump to line 2062 because the condition on line 2061 was never true
2062 break
2063 elif not self.peek("IDENTIFIER"):
2064 pass
2065 elif self.stab.contains(self.nt.value): 2065 ↛ 2066line 2065 didn't jump to line 2066 because the condition on line 2065 was never true
2066 n_sym = self.stab.lookup_assuming(self.mh,
2067 self.nt.value)
2068 if isinstance(n_sym, ast.Package):
2069 break
2070 elif self.cu.package.symbols.contains(self.nt.value):
2071 n_sym = self.cu.package.symbols.lookup_assuming(
2072 self.mh,
2073 self.nt.value)
2074 if isinstance(n_sym, ast.Record_Type):
2075 break
2076 self.advance()
2077 self.skip_until_newline()
2079 self.match_eof()
2081 for tok in self.lexer.tokens:
2082 if tok.kind == "COMMENT":
2083 self.cu.package.set_ast_link(tok)
2085 return ok