Coverage for trlc/parser.py: 96%
1123 statements
« prev ^ index » next coverage.py v7.10.7, created at 2026-05-21 07:21 +0000
« prev ^ index » next coverage.py v7.10.7, created at 2026-05-21 07:21 +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 # S_BRA here means a union type '[T1, T2, ...]', not array bounds.
453 if self.peek("S_BRA"):
454 # lobster-trace: LRM.union_type
455 field_type = self.parse_union_type()
456 else:
457 field_type = self.parse_qualified_name(self.default_scope,
458 ast.Type)
459 comp = ast.Composite_Component(name = field_name.value,
460 description = field_description,
461 location = field_name.location,
462 member_of = n_tuple,
463 n_typ = field_type,
464 optional = field_is_optional)
465 comp.set_ast_link(field_name)
466 if t_descr:
467 comp.set_ast_link(t_descr)
468 if field_is_optional:
469 comp.set_ast_link(t_optional)
471 return comp
473 def parse_tuple_declaration(self):
474 # lobster-trace: LRM.Tuple_Declaration
475 self.match_kw("tuple")
476 t_tuple = self.ct
477 name, description, t_descr = self.parse_described_name()
479 n_tuple = ast.Tuple_Type(name = name.value,
480 description = description,
481 location = name.location,
482 package = self.cu.package)
484 n_tuple.set_ast_link(t_tuple)
485 n_tuple.set_ast_link(name)
486 if t_descr:
487 n_tuple.set_ast_link(t_descr)
488 self.match("C_BRA")
489 n_tuple.set_ast_link(self.ct)
491 n_field = self.parse_tuple_field(
492 n_tuple,
493 optional_allowed = False,
494 optional_reason = "first field may not be optional",
495 optional_required = False)
496 n_tuple.components.register(self.mh, n_field)
498 has_separators = False
499 optional_required = False
500 separator_allowed = True
502 while self.peek_kw("separator") or self.peek("IDENTIFIER"):
503 if has_separators or self.peek_kw("separator"):
504 has_separators = True
505 self.match_kw("separator")
506 t_sep = self.ct
507 if not separator_allowed:
508 # lobster-trace: LRM.Tuple_Separators_All_Or_None
509 self.mh.error(self.ct.location,
510 "either all fields must be separated,"
511 " or none")
512 if self.peek("IDENTIFIER") or \ 512 ↛ 524line 512 didn't jump to line 524 because the condition on line 512 was always true
513 self.peek("AT") or \
514 self.peek("COLON") or \
515 self.peek("SEMICOLON"):
516 self.advance()
517 sep = ast.Separator(self.ct)
518 sep.set_ast_link(t_sep)
519 sep.set_ast_link(self.ct)
520 n_tuple.add_separator(sep)
521 else:
522 separator_allowed = False
523 # lobster-trace: LRM.Tuple_Optional_Requires_Separators
524 n_field = self.parse_tuple_field(
525 n_tuple,
526 optional_allowed = has_separators,
527 optional_reason = ("optional only permitted in tuples"
528 " with separators"),
529 optional_required = optional_required)
530 n_tuple.components.register(self.mh, n_field)
531 # lobster-trace: LRM.Tuple_Optional_Fields
532 optional_required |= n_field.optional
534 self.match("C_KET")
535 n_tuple.set_ast_link(self.ct)
537 # Final check to ban tuples with separators containing other
538 # tuples.
539 if has_separators:
540 # lobster-trace: LRM.Restricted_Tuple_Nesting
541 for n_field in n_tuple.components.values():
542 if isinstance(n_field.n_typ, ast.Tuple_Type) and \
543 n_field.n_typ.has_separators():
544 self.mh.error(
545 n_field.location,
546 "tuple type %s, which contains separators,"
547 " may not contain another tuple with separators"
548 % n_tuple.name)
550 # Late registration to avoid recursion in tuples
551 # lobster-trace: LRM.Tuple_Field_Types
552 self.cu.package.symbols.register(self.mh, n_tuple)
554 return n_tuple
556 def parse_union_type(self):
557 """Parse a union type declaration: '[' Type1 ',' Type2 ... ']'
559 The leading S_BRA must be the next token when called.
560 Returns an ast.Union_Type node.
561 """
562 # lobster-trace: LRM.union_type
563 # lobster-trace: LRM.Union_Type_No_Duplicates
564 # lobster-trace: LRM.Union_Type_Record_Types_Only
565 self.match("S_BRA")
566 t_s_bra = self.ct
568 union_type_entries = [] # list of (Record_Type, Location)
569 first_type = self.parse_qualified_name(self.default_scope,
570 ast.Record_Type)
571 first_type.set_ast_link(self.ct)
572 union_type_entries.append((first_type, self.ct.location))
574 while self.peek("COMMA"):
575 self.match("COMMA")
576 next_type = self.parse_qualified_name(
577 self.default_scope,
578 ast.Record_Type)
579 next_type.set_ast_link(self.ct)
580 union_type_entries.append((next_type, self.ct.location))
582 self.match("S_KET")
583 t_s_ket = self.ct
585 seen = {}
586 for t, loc in union_type_entries:
587 fqn = t.fully_qualified_name()
588 if fqn in seen:
589 self.mh.error(loc,
590 "duplicate type %s in union" % t.name,
591 fatal=False)
592 else:
593 seen[fqn] = loc
595 union_types = [t for t, _ in union_type_entries]
596 c_typ = ast.Union_Type(
597 location = t_s_bra.location,
598 types = union_types)
599 c_typ.set_ast_link(t_s_bra)
600 c_typ.set_ast_link(t_s_ket)
601 return c_typ
603 def parse_record_component(self, n_record):
604 assert isinstance(n_record, ast.Record_Type)
606 c_name, c_descr, t_descr = self.parse_described_name()
607 t_optional = None
608 c_optional = False
609 if self.peek_kw("optional"):
610 self.match_kw("optional")
611 t_optional = self.ct
612 c_optional = True
614 # S_BRA here means a union type '[T1, T2, ...]', not array bounds.
615 # Array bounds '[INTEGER..INTEGER]' are checked in the next block.
616 if self.peek("S_BRA"):
617 c_typ = self.parse_union_type()
618 else:
619 c_typ = self.parse_qualified_name(self.default_scope,
620 ast.Type)
621 c_typ.set_ast_link(self.ct)
623 if self.peek("S_BRA"):
624 self.match("S_BRA")
625 t_s_bra = self.ct
626 self.match("INTEGER")
627 t_lo = self.ct
628 a_lo = self.ct.value
629 loc_lo = self.ct.location
630 self.match("RANGE")
631 t_range = self.ct
632 a_loc = self.ct.location
633 a_hi = None
634 if self.peek("INTEGER"):
635 self.match("INTEGER")
636 a_hi = self.ct.value
637 elif self.peek("OPERATOR") and self.nt.value == "*": 637 ↛ 640line 637 didn't jump to line 640 because the condition on line 637 was always true
638 self.match("OPERATOR")
639 else:
640 self.mh.error(self.nt.location,
641 "expected INTEGER or * for upper bound")
642 t_hi = self.ct
643 loc_hi = self.ct.location
644 self.match("S_KET")
645 t_s_ket = self.ct
646 c_typ = ast.Array_Type(location = a_loc,
647 element_type = c_typ,
648 lower_bound = a_lo,
649 upper_bound = a_hi,
650 loc_lower = loc_lo,
651 loc_upper = loc_hi)
652 c_typ.set_ast_link(t_s_bra)
653 c_typ.set_ast_link(t_lo)
654 c_typ.set_ast_link(t_range)
655 c_typ.set_ast_link(t_hi)
656 c_typ.set_ast_link(t_s_ket)
658 c_comp = ast.Composite_Component(name = c_name.value,
659 description = c_descr,
660 location = c_name.location,
661 member_of = n_record,
662 n_typ = c_typ,
663 optional = c_optional)
664 c_comp.set_ast_link(c_name)
665 if t_descr:
666 c_comp.set_ast_link(t_descr)
667 if c_optional:
668 c_comp.set_ast_link(t_optional)
670 return c_comp
672 def parse_record_declaration(self):
673 t_abstract = None
674 t_final = None
675 is_abstract = False
676 is_final = False
677 if self.peek_kw("abstract"):
678 self.match_kw("abstract")
679 t_abstract = self.ct
680 is_abstract = True
681 elif self.peek_kw("final"):
682 self.match_kw("final")
683 t_final = self.ct
684 is_final = True
686 self.match_kw("type")
687 t_type = self.ct
688 name, description, t_description = self.parse_described_name()
690 if self.peek_kw("extends"):
691 self.match_kw("extends")
692 t_extends = self.ct
693 root_record = self.parse_qualified_name(self.default_scope,
694 ast.Record_Type)
695 root_record.set_ast_link(t_extends)
696 root_record.set_ast_link(self.ct)
697 else:
698 root_record = None
700 if self.lint_mode and \
701 root_record and root_record.is_final and \
702 not is_final:
703 self.mh.check(name.location,
704 "consider clarifying that this record is final",
705 "clarify_final",
706 ("Parent record %s is final, making this record\n"
707 "also final. Marking it explicitly as final\n"
708 "clarifies this to casual readers." %
709 root_record.fully_qualified_name()))
711 record = ast.Record_Type(name = name.value,
712 description = description,
713 location = name.location,
714 package = self.cu.package,
715 n_parent = root_record,
716 is_abstract = is_abstract)
717 self.cu.package.symbols.register(self.mh, record)
718 if is_abstract:
719 record.set_ast_link(t_abstract)
720 if is_final:
721 record.set_ast_link(t_final)
722 record.set_ast_link(t_type)
723 record.set_ast_link(name)
724 if t_description:
725 record.set_ast_link(t_description)
727 self.match("C_BRA")
728 record.set_ast_link(self.ct)
729 while not self.peek("C_KET"):
730 if self.peek_kw("freeze"):
731 self.match_kw("freeze")
732 t_freeze = self.ct
733 self.match("IDENTIFIER")
734 n_comp = record.components.lookup(self.mh,
735 self.ct,
736 ast.Composite_Component)
737 if record.is_frozen(n_comp):
738 n_value = record.get_freezing_expression(n_comp)
739 self.mh.error(
740 self.ct.location,
741 "duplicate freezing of %s, previously frozen at %s" %
742 (n_comp.name,
743 self.mh.cross_file_reference(n_value.location)))
744 n_comp.set_ast_link(t_freeze)
745 n_comp.set_ast_link(self.ct)
746 self.match("ASSIGN")
747 n_comp.set_ast_link(self.ct)
748 n_value = self.parse_value(n_comp.n_typ)
749 n_value.set_ast_link(self.ct)
751 record.frozen[n_comp.name] = n_value
753 else:
754 n_comp = self.parse_record_component(record)
755 if record.is_final:
756 self.mh.error(n_comp.location,
757 "cannot declare new components in"
758 " final record type")
759 else:
760 record.components.register(self.mh, n_comp)
762 self.match("C_KET")
763 record.set_ast_link(self.ct)
765 # Finally mark record final if applicable
766 if is_final:
767 record.is_final = True
769 return record
771 def parse_expression(self, scope):
772 # lobster-trace: LRM.Expression
773 assert isinstance(scope, ast.Scope)
775 n_lhs = self.parse_relation(scope)
777 if self.peek_kw("and"):
778 while self.peek_kw("and"):
779 self.match_kw("and")
780 t_op = self.ct
781 a_op = ast.Binary_Operator.LOGICAL_AND
782 t_op.ast_link = a_op
783 n_rhs = self.parse_relation(scope)
784 n_lhs = ast.Binary_Expression(
785 mh = self.mh,
786 location = t_op.location,
787 typ = self.builtin_bool,
788 operator = a_op,
789 n_lhs = n_lhs,
790 n_rhs = n_rhs)
792 elif self.peek_kw("or"):
793 while self.peek_kw("or"):
794 self.match_kw("or")
795 t_op = self.ct
796 a_op = ast.Binary_Operator.LOGICAL_OR
797 t_op.ast_link = a_op
798 n_rhs = self.parse_relation(scope)
799 n_lhs = ast.Binary_Expression(
800 mh = self.mh,
801 location = t_op.location,
802 typ = self.builtin_bool,
803 operator = a_op,
804 n_lhs = n_lhs,
805 n_rhs = n_rhs)
807 elif self.peek_kw("xor"):
808 self.match_kw("xor")
809 t_op = self.ct
810 a_op = ast.Binary_Operator.LOGICAL_XOR
811 t_op.ast_link = a_op
812 n_rhs = self.parse_relation(scope)
813 n_lhs = ast.Binary_Expression(
814 mh = self.mh,
815 location = t_op.location,
816 typ = self.builtin_bool,
817 operator = a_op,
818 n_lhs = n_lhs,
819 n_rhs = n_rhs)
821 elif self.peek_kw("implies"):
822 self.match_kw("implies")
823 t_op = self.ct
824 a_op = ast.Binary_Operator.LOGICAL_IMPLIES
825 t_op.ast_link = a_op
826 n_rhs = self.parse_relation(scope)
827 n_lhs = ast.Binary_Expression(
828 mh = self.mh,
829 location = t_op.location,
830 typ = self.builtin_bool,
831 operator = a_op,
832 n_lhs = n_lhs,
833 n_rhs = n_rhs)
835 return n_lhs
837 def parse_relation(self, scope):
838 # lobster-trace: LRM.Relation
839 # lobster-trace: LRM.Operators
840 assert isinstance(scope, ast.Scope)
841 relop_mapping = {"==" : ast.Binary_Operator.COMP_EQ,
842 "!=" : ast.Binary_Operator.COMP_NEQ,
843 "<" : ast.Binary_Operator.COMP_LT,
844 "<=" : ast.Binary_Operator.COMP_LEQ,
845 ">" : ast.Binary_Operator.COMP_GT,
846 ">=" : ast.Binary_Operator.COMP_GEQ}
847 assert set(relop_mapping) == set(Parser.COMPARISON_OPERATOR)
849 n_lhs = self.parse_simple_expression(scope)
851 if self.peek("OPERATOR") and \
852 self.nt.value in Parser.COMPARISON_OPERATOR:
853 self.match("OPERATOR")
854 t_op = self.ct
855 a_op = relop_mapping[t_op.value]
856 t_op.ast_link = a_op
857 n_rhs = self.parse_simple_expression(scope)
858 return ast.Binary_Expression(
859 mh = self.mh,
860 location = t_op.location,
861 typ = self.builtin_bool,
862 operator = a_op,
863 n_lhs = n_lhs,
864 n_rhs = n_rhs)
866 elif self.peek_kw("not") or self.peek_kw("in"):
867 if self.peek_kw("not"):
868 self.match_kw("not")
869 t_not = self.ct
870 else:
871 t_not = None
873 self.match_kw("in")
874 t_in = self.ct
876 n_a = self.parse_simple_expression(scope)
877 t_n_a = self.ct
878 if self.peek("RANGE"):
879 self.match("RANGE")
880 t_range = self.ct
881 n_b = self.parse_simple_expression(scope)
882 n_b.set_ast_link(self.ct)
883 n_a.set_ast_link(t_n_a)
884 rv = ast.Range_Test(
885 mh = self.mh,
886 location = t_in.location,
887 typ = self.builtin_bool,
888 n_lhs = n_lhs,
889 n_lower = n_a,
890 n_upper = n_b)
891 rv.set_ast_link(t_range)
892 rv.set_ast_link(t_in)
894 elif isinstance(n_a.typ, ast.Builtin_String):
895 rv = ast.Binary_Expression(
896 mh = self.mh,
897 location = t_in.location,
898 typ = self.builtin_bool,
899 operator = ast.Binary_Operator.STRING_CONTAINS,
900 n_lhs = n_lhs,
901 n_rhs = n_a)
902 rv.set_ast_link(t_in)
904 elif isinstance(n_a.typ, ast.Array_Type): 904 ↛ 916line 904 didn't jump to line 916 because the condition on line 904 was always true
905 a_op = ast.Binary_Operator.ARRAY_CONTAINS
906 t_in.ast_link = a_op
907 rv = ast.Binary_Expression(
908 mh = self.mh,
909 location = t_in.location,
910 typ = self.builtin_bool,
911 operator = a_op,
912 n_lhs = n_lhs,
913 n_rhs = n_a)
915 else:
916 self.mh.error(
917 n_a.location,
918 "membership test only defined for Strings and Arrays,"
919 " not for %s" % n_a.typ.name)
921 if t_not is not None:
922 a_unary_op = ast.Unary_Operator.LOGICAL_NOT
923 t_not.ast_link = a_unary_op
924 rv = ast.Unary_Expression(
925 mh = self.mh,
926 location = t_not.location,
927 typ = self.builtin_bool,
928 operator = a_unary_op,
929 n_operand = rv)
931 return rv
933 else:
934 return n_lhs
936 def parse_simple_expression(self, scope):
937 # lobster-trace: LRM.Simple_Expression
938 # lobster-trace: LRM.Operators
939 # lobster-trace: LRM.Unary_Minus_Parsing
940 assert isinstance(scope, ast.Scope)
941 un_add_map = {"+" : ast.Unary_Operator.PLUS,
942 "-" : ast.Unary_Operator.MINUS}
943 bin_add_map = {"+" : ast.Binary_Operator.PLUS,
944 "-" : ast.Binary_Operator.MINUS}
945 assert set(un_add_map) == set(Parser.ADDING_OPERATOR)
946 assert set(bin_add_map) == set(Parser.ADDING_OPERATOR)
948 if self.peek("OPERATOR") and \
949 self.nt.value in Parser.ADDING_OPERATOR:
950 self.match("OPERATOR")
951 t_unary = self.ct
952 a_unary = un_add_map[t_unary.value]
953 t_unary.ast_link = a_unary
954 has_explicit_brackets = self.peek("BRA")
955 else:
956 t_unary = None
958 n_lhs = self.parse_term(scope)
959 if t_unary:
960 # pylint: disable=possibly-used-before-assignment
961 if self.lint_mode and \
962 isinstance(n_lhs, ast.Binary_Expression) and \
963 not has_explicit_brackets:
964 self.mh.check(t_unary.location,
965 "expression means -(%s), place explicit "
966 "brackets to clarify intent" %
967 n_lhs.to_string(),
968 "unary_minus_precedence")
970 n_lhs = ast.Unary_Expression(
971 mh = self.mh,
972 location = t_unary.location,
973 typ = n_lhs.typ,
974 operator = a_unary,
975 n_operand = n_lhs)
977 if isinstance(n_lhs.typ, ast.Builtin_String):
978 rtyp = self.builtin_str
979 else:
980 rtyp = n_lhs.typ
982 while self.peek("OPERATOR") and \
983 self.nt.value in Parser.ADDING_OPERATOR:
984 self.match("OPERATOR")
985 t_op = self.ct
986 a_op = bin_add_map[t_op.value]
987 t_op.ast_link = a_op
988 n_rhs = self.parse_term(scope)
989 n_lhs = ast.Binary_Expression(
990 mh = self.mh,
991 location = t_op.location,
992 typ = rtyp,
993 operator = a_op,
994 n_lhs = n_lhs,
995 n_rhs = n_rhs)
997 return n_lhs
999 def parse_term(self, scope):
1000 # lobster-trace: LRM.Term
1001 # lobster-trace: LRM.Operators
1002 assert isinstance(scope, ast.Scope)
1003 mul_map = {"*" : ast.Binary_Operator.TIMES,
1004 "/" : ast.Binary_Operator.DIVIDE,
1005 "%" : ast.Binary_Operator.REMAINDER}
1006 assert set(mul_map) == set(Parser.MULTIPLYING_OPERATOR)
1008 n_lhs = self.parse_factor(scope)
1009 while self.peek("OPERATOR") and \
1010 self.nt.value in Parser.MULTIPLYING_OPERATOR:
1011 self.match("OPERATOR")
1012 t_op = self.ct
1013 a_op = mul_map[t_op.value]
1014 t_op.ast_link = a_op
1015 n_rhs = self.parse_factor(scope)
1016 n_lhs = ast.Binary_Expression(
1017 mh = self.mh,
1018 location = t_op.location,
1019 typ = n_lhs.typ,
1020 operator = a_op,
1021 n_lhs = n_lhs,
1022 n_rhs = n_rhs)
1024 return n_lhs
1026 def parse_factor(self, scope):
1027 # lobster-trace: LRM.Factor
1028 assert isinstance(scope, ast.Scope)
1030 if self.peek_kw("not"):
1031 self.match_kw("not")
1032 t_op = self.ct
1033 n_operand = self.parse_primary(scope)
1034 a_not = ast.Unary_Operator.LOGICAL_NOT
1035 t_op.ast_link = a_not
1036 return ast.Unary_Expression(
1037 mh = self.mh,
1038 location = t_op.location,
1039 typ = self.builtin_bool,
1040 operator = a_not,
1041 n_operand = n_operand)
1043 elif self.peek_kw("abs"):
1044 self.match_kw("abs")
1045 t_op = self.ct
1046 n_operand = self.parse_primary(scope)
1047 a_abs = ast.Unary_Operator.ABSOLUTE_VALUE
1048 t_op.ast_link = a_abs
1049 return ast.Unary_Expression(
1050 mh = self.mh,
1051 location = t_op.location,
1052 typ = n_operand.typ,
1053 operator = a_abs,
1054 n_operand = n_operand)
1056 else:
1057 n_lhs = self.parse_primary(scope)
1058 if self.peek("OPERATOR") and self.nt.value == "**":
1059 self.match("OPERATOR")
1060 t_op = self.ct
1061 n_rhs = self.parse_primary(scope)
1062 rhs_value = n_rhs.evaluate(self.mh, None, None)
1063 a_binary = ast.Binary_Operator.POWER
1064 t_op.ast_link = a_binary
1065 n_lhs = ast.Binary_Expression(
1066 mh = self.mh,
1067 location = t_op.location,
1068 typ = n_lhs.typ,
1069 operator = a_binary,
1070 n_lhs = n_lhs,
1071 n_rhs = n_rhs)
1072 if rhs_value.value < 0: 1072 ↛ 1073line 1072 didn't jump to line 1073 because the condition on line 1072 was never true
1073 self.mh.error(n_rhs.location,
1074 "exponent must not be negative")
1075 return n_lhs
1077 def parse_primary(self, scope):
1078 # lobster-trace: LRM.Primary
1079 assert isinstance(scope, ast.Scope)
1081 if self.peek("INTEGER"):
1082 # lobster-trace: LRM.Integer_Values
1083 self.match("INTEGER")
1084 int_lit = ast.Integer_Literal(self.ct, self.builtin_int)
1085 int_lit.set_ast_link(self.ct)
1086 return int_lit
1088 elif self.peek("DECIMAL"):
1089 # lobster-trace: LRM.Decimal_Values
1090 self.match("DECIMAL")
1091 dec_lit = ast.Decimal_Literal(self.ct, self.builtin_decimal)
1092 dec_lit.set_ast_link(self.ct)
1093 return dec_lit
1095 elif self.peek("STRING"):
1096 # lobster-trace: LRM.String_Values
1097 self.match("STRING")
1098 string_lit = ast.String_Literal(self.ct, self.builtin_str)
1099 string_lit.set_ast_link(self.ct)
1100 return string_lit
1102 elif self.peek_kw("true") or self.peek_kw("false"):
1103 # lobster-trace: LRM.Boolean_Values
1104 self.match("KEYWORD")
1105 bool_lit = ast.Boolean_Literal(self.ct, self.builtin_bool)
1106 bool_lit.set_ast_link(self.ct)
1107 return bool_lit
1109 elif self.peek_kw("null"):
1110 self.match_kw("null")
1111 null_lit = ast.Null_Literal(self.ct)
1112 null_lit.set_ast_link(self.ct)
1113 return null_lit
1115 elif self.peek("BRA"):
1116 self.match("BRA")
1117 t_bra = self.ct
1118 if self.peek_kw("forall") or self.peek_kw("exists"):
1119 rv = self.parse_quantified_expression(scope)
1120 elif self.peek_kw("if"):
1121 rv = self.parse_conditional_expression(scope)
1122 else:
1123 rv = self.parse_expression(scope)
1124 rv.set_ast_link(t_bra)
1125 self.match("KET")
1126 rv.set_ast_link(self.ct)
1127 return rv
1129 else:
1130 return self.parse_name(scope)
1132 def parse_quantified_expression(self, scope):
1133 # lobster-trace: LRM.Quantified_Expression
1134 assert isinstance(scope, ast.Scope)
1136 if self.peek_kw("forall"):
1137 self.match_kw("forall")
1138 t_quantified = self.ct
1139 universal = True
1140 else:
1141 self.match_kw("exists")
1142 t_quantified = self.ct
1143 universal = False
1144 loc = self.ct.location
1145 self.match("IDENTIFIER")
1146 t_qv = self.ct
1147 if scope.contains(t_qv.value):
1148 # lobster-trace: LRM.Quantification_Naming_Scope
1149 pdef = scope.lookup(self.mh, t_qv)
1150 self.mh.error(t_qv.location,
1151 "shadows %s %s from %s" %
1152 (pdef.__class__.__name__,
1153 pdef.name,
1154 self.mh.cross_file_reference(pdef.location)))
1155 self.match_kw("in")
1156 t_in = self.ct
1157 self.match("IDENTIFIER")
1158 field = scope.lookup(self.mh, self.ct, ast.Composite_Component)
1159 n_source = ast.Name_Reference(self.ct.location,
1160 field)
1161 n_source.set_ast_link(self.ct)
1162 if not isinstance(field.n_typ, ast.Array_Type):
1163 # lobster-trace: LRM.Quantification_Object
1164 self.mh.error(self.ct.location,
1165 "you can only quantify over arrays")
1166 n_var = ast.Quantified_Variable(t_qv.value,
1167 t_qv.location,
1168 field.n_typ.element_type)
1169 n_var.set_ast_link(t_qv)
1170 self.match("ARROW")
1171 t_arrow = self.ct
1173 new_table = ast.Symbol_Table()
1174 new_table.register(self.mh, n_var)
1175 scope.push(new_table)
1176 n_expr = self.parse_expression(scope)
1177 scope.pop()
1179 quantified_expression = ast.Quantified_Expression(
1180 mh = self.mh,
1181 location = loc,
1182 typ = self.builtin_bool,
1183 universal = universal,
1184 n_variable = n_var,
1185 n_source = n_source,
1186 n_expr = n_expr)
1188 quantified_expression.set_ast_link(t_quantified)
1189 quantified_expression.set_ast_link(t_in)
1190 quantified_expression.set_ast_link(t_arrow)
1192 return quantified_expression
1194 def parse_conditional_expression(self, scope):
1195 # lobster-trace: LRM.Conditional_Expression
1196 # lobster-trace: LRM.Restricted_Null
1197 assert isinstance(scope, ast.Scope)
1199 self.match_kw("if")
1200 t_if = self.ct
1201 if_cond = self.parse_expression(scope)
1202 self.match_kw("then")
1203 t_then = self.ct
1204 if_expr = self.parse_expression(scope)
1205 if if_expr.typ is None:
1206 self.mh.error(if_expr.location,
1207 "null is not permitted here")
1208 if_action = ast.Action(self.mh, t_if, if_cond, if_expr)
1210 rv = ast.Conditional_Expression(location = t_if.location,
1211 if_action = if_action)
1212 if_action.set_ast_link(t_if)
1213 if_action.set_ast_link(t_then)
1215 while self.peek_kw("elsif"):
1216 self.match_kw("elsif")
1217 t_elsif = self.ct
1218 elsif_cond = self.parse_expression(scope)
1219 self.match_kw("then")
1220 t_then = self.ct
1221 elsif_expr = self.parse_expression(scope)
1222 elsif_action = ast.Action(self.mh, t_elsif, elsif_cond, elsif_expr)
1223 elsif_action.set_ast_link(t_elsif)
1224 elsif_action.set_ast_link(t_then)
1225 rv.add_elsif(self.mh, elsif_action)
1227 self.match_kw("else")
1228 rv.set_ast_link(self.ct)
1229 else_expr = self.parse_expression(scope)
1230 rv.set_else_part(self.mh, else_expr)
1232 return rv
1234 def parse_builtin(self, scope, n_name, t_name):
1235 # lobster-trace: LRM.Builtin_Functions
1236 # lobster-trace: LRM.Builtin_Type_Conversion_Functions
1237 assert isinstance(scope, ast.Scope)
1238 assert isinstance(n_name, (ast.Builtin_Function,
1239 ast.Builtin_Numeric_Type))
1240 assert isinstance(t_name, Token)
1242 # Parse the arguments.
1243 parameters = []
1244 n_name.set_ast_link(self.ct)
1245 self.match("BRA")
1246 n_name.set_ast_link(self.ct)
1247 while not self.peek("KET"): 1247 ↛ 1258line 1247 didn't jump to line 1258 because the condition on line 1247 was always true
1248 exp = self.parse_expression(scope)
1249 if not self.ct.ast_link: 1249 ↛ 1250line 1249 didn't jump to line 1250 because the condition on line 1249 was never true
1250 exp.set_ast_link(self.ct)
1251 parameters.append(exp)
1253 if self.peek("COMMA"): 1253 ↛ 1257line 1253 didn't jump to line 1257 because the condition on line 1253 was always true
1254 self.match("COMMA")
1255 n_name.set_ast_link(self.ct)
1256 else:
1257 break
1258 self.match("KET")
1259 n_name.set_ast_link(self.ct)
1261 # Enforce arity
1262 if isinstance(n_name, ast.Builtin_Function):
1263 required_arity = n_name.arity
1264 precise = not n_name.arity_at_least
1265 else:
1266 required_arity = 1
1267 precise = True
1269 if precise:
1270 if required_arity != len(parameters):
1271 self.mh.error(t_name.location,
1272 "function requires %u parameters" %
1273 n_name.arity)
1274 else:
1275 if required_arity > len(parameters): 1275 ↛ 1276line 1275 didn't jump to line 1276 because the condition on line 1275 was never true
1276 self.mh.error(t_name.location,
1277 "function requires at least %u parameters" %
1278 n_name.arity)
1280 # Enforce types
1281 if n_name.name == "len":
1282 if isinstance(parameters[0].typ, ast.Builtin_String):
1283 return ast.Unary_Expression(
1284 mh = self.mh,
1285 location = t_name.location,
1286 typ = self.builtin_int,
1287 operator = ast.Unary_Operator.STRING_LENGTH,
1288 n_operand = parameters[0])
1289 else:
1290 return ast.Unary_Expression(
1291 mh = self.mh,
1292 location = t_name.location,
1293 typ = self.builtin_int,
1294 operator = ast.Unary_Operator.ARRAY_LENGTH,
1295 n_operand = parameters[0])
1297 elif n_name.name in ("startswith",
1298 "endswith"):
1299 return ast.Binary_Expression(
1300 mh = self.mh,
1301 location = t_name.location,
1302 typ = self.builtin_bool,
1303 operator = (ast.Binary_Operator.STRING_STARTSWITH
1304 if "startswith" in n_name.name
1305 else ast.Binary_Operator.STRING_ENDSWITH),
1306 n_lhs = parameters[0],
1307 n_rhs = parameters[1])
1309 elif n_name.name == "matches":
1310 parameters[1].ensure_type(self.mh, ast.Builtin_String)
1311 try:
1312 # lobster-trace: LRM.Static_Regular_Expression
1313 # scope is None on purpose to enforce static context
1314 value = parameters[1].evaluate(self.mh, None, None)
1315 assert isinstance(value.typ, ast.Builtin_String)
1316 re.compile(value.value)
1317 except re.error as err:
1318 self.mh.error(value.location,
1319 str(err))
1320 return ast.Binary_Expression(
1321 mh = self.mh,
1322 location = t_name.location,
1323 typ = self.builtin_bool,
1324 operator = ast.Binary_Operator.STRING_REGEX,
1325 n_lhs = parameters[0],
1326 n_rhs = parameters[1])
1328 elif n_name.name == "oneof":
1329 return ast.OneOf_Expression(
1330 mh = self.mh,
1331 location = t_name.location,
1332 typ = self.builtin_bool,
1333 choices = parameters)
1335 elif isinstance(n_name, ast.Builtin_Numeric_Type):
1336 parameters[0].ensure_type(self.mh, ast.Builtin_Numeric_Type)
1337 if isinstance(n_name, ast.Builtin_Integer):
1338 return ast.Unary_Expression(
1339 mh = self.mh,
1340 location = t_name.location,
1341 typ = self.builtin_int,
1342 operator = ast.Unary_Operator.CONVERSION_TO_INT,
1343 n_operand = parameters[0])
1344 elif isinstance(n_name, ast.Builtin_Decimal):
1345 return ast.Unary_Expression(
1346 mh = self.mh,
1347 location = t_name.location,
1348 typ = self.builtin_decimal,
1349 operator = ast.Unary_Operator.CONVERSION_TO_DECIMAL,
1350 n_operand = parameters[0])
1351 else:
1352 self.mh.ice_loc(t_name.location,
1353 "unexpected type conversion")
1355 else:
1356 self.mh.ice_loc(t_name.location,
1357 "unexpected builtin")
1359 def parse_name(self, scope):
1360 # lobster-trace: LRM.Names
1362 # This is a bit more complex. The grammar is:
1363 #
1364 # qualified_name ::= [ IDENTIFIER_package_name '.' ] IDENTIFIER_name
1365 #
1366 # name ::= qualified_name
1367 # | name '.' IDENTIFIER
1368 # | name '[' expression ']'
1369 # | name '(' parameter_list ')'
1370 #
1371 # parameter_list ::= expression { ',' expression }
1373 assert isinstance(scope, ast.Scope)
1375 # All names start with a (qualified) identifier. We parse that
1376 # first. There is a special complication for functions, as
1377 # builtin functions (e.g. len) can shadow record
1378 # components. However as functions cannot be stored in
1379 # components the true grammar for function calls is always
1380 # IDENTIFIER '('; so we can slightly special case this.
1382 # lobster-trace: LRM.Builtin_Functions
1383 # lobster-trace: LRM.Builtin_Type_Conversion_Functions
1384 self.match("IDENTIFIER")
1385 if self.peek("BRA"):
1386 # If we follow our name with brackets
1387 # immediately, we have a builtin function call.
1388 n_name = self.stab.lookup(self.mh,
1389 self.ct)
1390 if not isinstance(n_name, (ast.Builtin_Function, 1390 ↛ 1392line 1390 didn't jump to line 1392 because the condition on line 1390 was never true
1391 ast.Builtin_Numeric_Type)):
1392 self.mh.error(self.ct.location,
1393 "not a valid builtin function "
1394 "or numeric type")
1395 else:
1396 n_name = self.parse_qualified_name(scope, match_ident=False)
1398 # Enum literals are a bit different, so we deal with them
1399 # first.
1400 if isinstance(n_name, ast.Enumeration_Type):
1401 n_name.set_ast_link(self.ct)
1402 self.match("DOT")
1403 n_name.set_ast_link(self.ct)
1404 self.match("IDENTIFIER")
1405 lit = n_name.literals.lookup(self.mh,
1406 self.ct,
1407 ast.Enumeration_Literal_Spec)
1408 enum_lit = ast.Enumeration_Literal(location = self.ct.location,
1409 literal = lit)
1410 enum_lit.set_ast_link(self.ct)
1411 return enum_lit
1413 # Anything that remains is either a function call or an actual
1414 # name. Let's just enforce this for sanity.
1415 if not isinstance(n_name, (ast.Builtin_Function, 1415 ↛ 1420line 1415 didn't jump to line 1420 because the condition on line 1415 was never true
1416 ast.Builtin_Numeric_Type,
1417 ast.Composite_Component,
1418 ast.Quantified_Variable,
1419 )):
1420 self.mh.error(self.ct.location,
1421 "%s %s is not a valid name" %
1422 (n_name.__class__.__name__,
1423 n_name.name))
1425 # Right now function calls and type conversions must be
1426 # top-level, so let's get these out of the way as well.
1427 if isinstance(n_name, (ast.Builtin_Function,
1428 ast.Builtin_Numeric_Type)):
1429 # lobster-trace: LRM.Builtin_Functions
1430 # lobster-trace: LRM.Builtin_Type_Conversion_Functions
1431 return self.parse_builtin(scope, n_name, self.ct)
1433 assert isinstance(n_name, (ast.Composite_Component,
1434 ast.Quantified_Variable))
1436 # We now process the potentially recursive part:
1437 # | name '.' IDENTIFIER
1438 # | name '[' expression ']'
1439 n_name = ast.Name_Reference(location = self.ct.location,
1440 entity = n_name)
1441 n_name.set_ast_link(self.ct)
1442 while self.peek("DOT") or self.peek("S_BRA"):
1443 if self.peek("DOT"):
1444 if not isinstance(n_name.typ, (ast.Tuple_Type, 1444 ↛ 1448line 1444 didn't jump to line 1448 because the condition on line 1444 was never true
1445 ast.Record_Type,
1446 ast.Union_Type)):
1447 # lobster-trace: LRM.Valid_Index_Prefixes
1448 self.mh.error(n_name.location,
1449 "expression '%s' has type %s, "
1450 "which is not a tuple, record, or union" %
1451 (n_name.to_string(),
1452 n_name.typ.name))
1454 self.match("DOT")
1455 t_dot = self.ct
1456 self.match("IDENTIFIER")
1457 t_field = self.ct
1459 is_union_access = isinstance(n_name.typ,
1460 ast.Union_Type)
1461 is_universal = True
1463 if is_union_access:
1464 # lobster-trace: LRM.Union_Type_Field_Access
1465 # lobster-trace: LRM.Union_Type_Field_Type_Conflict
1466 # lobster-trace: LRM.Union_Type_Field_Access_Validity
1467 field_name = t_field.value
1468 field_map = n_name.typ.get_field_map()
1469 if field_name not in field_map:
1470 self.mh.error(
1471 t_field.location,
1472 "field %s does not exist in any member"
1473 " of union type %s" %
1474 (field_name, n_name.typ.name))
1475 info = field_map[field_name]
1476 if info["n_typ"] is None:
1477 self.mh.error(
1478 t_field.location,
1479 "field %s has conflicting types in"
1480 " members of union type %s" %
1481 (field_name, n_name.typ.name))
1482 is_universal = info["count"] == info["total"]
1483 # lobster-trace: LRM.Union_Type_Partial_Field_Access
1484 if self.lint_mode and not is_universal:
1485 self.mh.check(
1486 t_field.location,
1487 "field %s exists only in %u of %u"
1488 " members of union type %s;"
1489 " accessing it on other members"
1490 " returns null" %
1491 (field_name,
1492 info["count"],
1493 info["total"],
1494 n_name.typ.name),
1495 "union_partial_field_access")
1496 n_field = info["component"]
1497 else:
1498 n_field = n_name.typ.components.lookup(
1499 self.mh,
1500 t_field,
1501 ast.Composite_Component)
1503 n_field.set_ast_link(t_field)
1504 n_name = ast.Field_Access_Expression(
1505 mh = self.mh,
1506 location = t_field.location,
1507 n_prefix = n_name,
1508 n_field = n_field,
1509 is_union_access = is_union_access,
1510 is_universal = is_universal)
1511 n_name.set_ast_link(t_dot)
1513 elif self.peek("S_BRA"): 1513 ↛ 1442line 1513 didn't jump to line 1442 because the condition on line 1513 was always true
1514 if not isinstance(n_name.typ, ast.Array_Type):
1515 self.mh.error(n_name.location,
1516 "expression '%s' has type %s, "
1517 "which is not an array" %
1518 (n_name.to_string(),
1519 n_name.typ.name))
1521 self.match("S_BRA")
1522 t_bracket = self.ct
1523 n_index = self.parse_expression(scope)
1524 self.match("S_KET")
1525 a_binary = ast.Binary_Operator.INDEX
1526 t_bracket.ast_link = a_binary
1527 self.ct.ast_link = a_binary
1529 n_name = ast.Binary_Expression(
1530 mh = self.mh,
1531 location = t_bracket.location,
1532 typ = n_name.typ.element_type,
1533 operator = a_binary,
1534 n_lhs = n_name,
1535 n_rhs = n_index)
1537 return n_name
1539 def parse_check_block(self):
1540 # lobster-trace: LRM.Check_Block
1541 t_severity = None
1542 self.match_kw("checks")
1543 t_checks = self.ct
1544 self.match("IDENTIFIER")
1545 # lobster-trace: LRM.Applicable_Types
1546 # lobster-trace: LRM.Applicable_Components
1547 n_ctype = self.cu.package.symbols.lookup(self.mh,
1548 self.ct,
1549 ast.Composite_Type)
1550 n_check_block = ast.Check_Block(location = self.ct.location,
1551 n_typ = n_ctype)
1552 n_check_block.set_ast_link(t_checks)
1553 n_ctype.set_ast_link(self.ct)
1554 scope = ast.Scope()
1555 scope.push(self.stab)
1556 scope.push(self.cu.package.symbols)
1557 scope.push(n_ctype.components)
1558 self.match("C_BRA")
1559 n_check_block.set_ast_link(self.ct)
1560 while not self.peek("C_KET"):
1561 c_expr = self.parse_expression(scope)
1562 if not isinstance(c_expr.typ, ast.Builtin_Boolean): 1562 ↛ 1563line 1562 didn't jump to line 1563 because the condition on line 1562 was never true
1563 self.mh.error(c_expr.location,
1564 "check expression must be Boolean")
1566 self.match("COMMA")
1567 t_first_comma = self.ct
1568 if self.peek("KEYWORD"):
1569 self.match("KEYWORD")
1570 t_severity = self.ct
1571 if self.ct.value not in ("warning", "error", "fatal"): 1571 ↛ 1572line 1571 didn't jump to line 1572 because the condition on line 1571 was never true
1572 self.mh.error(self.ct.location,
1573 "expected warning|error|fatal")
1574 c_sev = self.ct.value
1575 else:
1576 c_sev = "error"
1578 self.match("STRING")
1579 if "\n" in self.ct.value:
1580 # lobster-trace: LRM.No_Newlines_In_Message
1581 self.mh.error(self.ct.location,
1582 "error message must not contain a newline",
1583 fatal = False)
1584 t_msg = self.ct
1586 has_extrainfo = False
1587 has_anchor = False
1588 if self.peek("COMMA"):
1589 self.match("COMMA")
1590 t_second_comma = self.ct
1591 if self.peek("IDENTIFIER"):
1592 has_anchor = True
1593 elif self.peek("STRING"): 1593 ↛ 1596line 1593 didn't jump to line 1596 because the condition on line 1593 was always true
1594 has_extrainfo = True
1595 else:
1596 self.mh.error(self.nt.location,
1597 "expected either a details string or"
1598 " identifier to anchor the check message")
1600 if has_extrainfo:
1601 self.match("STRING")
1602 t_extrainfo = self.ct
1603 c_extrainfo = self.ct.value
1605 if self.peek("COMMA"): 1605 ↛ 1606line 1605 didn't jump to line 1606 because the condition on line 1605 was never true
1606 self.match("COMMA")
1607 t_third_comma = self.ct
1608 has_anchor = True
1610 else:
1611 c_extrainfo = None
1613 if has_anchor:
1614 self.match("IDENTIFIER")
1615 t_anchor = self.ct
1616 c_anchor = n_ctype.components.lookup(self.mh,
1617 self.ct,
1618 ast.Composite_Component)
1619 else:
1620 c_anchor = None
1622 n_check = ast.Check(n_type = n_ctype,
1623 n_expr = c_expr,
1624 n_anchor = c_anchor,
1625 severity = c_sev,
1626 t_message = t_msg,
1627 extrainfo = c_extrainfo)
1629 # pylint: disable=possibly-used-before-assignment
1630 # pylint: disable=used-before-assignment
1632 n_check.set_ast_link(t_first_comma)
1633 if t_severity:
1634 n_check.set_ast_link(t_severity)
1635 n_check.set_ast_link(t_msg)
1636 if c_extrainfo or c_anchor:
1637 n_check.set_ast_link(t_second_comma)
1638 if c_extrainfo:
1639 n_check.set_ast_link(t_extrainfo)
1640 if c_anchor:
1641 c_anchor.set_ast_link(t_anchor)
1642 if c_anchor and c_extrainfo: 1642 ↛ 1643line 1642 didn't jump to line 1643 because the condition on line 1642 was never true
1643 n_check.set_ast_link(t_third_comma)
1645 n_ctype.add_check(n_check)
1646 n_check_block.add_check(n_check)
1648 assert scope.size() == 3
1650 self.match("C_KET")
1651 n_check_block.set_ast_link(self.ct)
1653 return n_check_block
1655 def parse_section_declaration(self):
1656 # lobster-trace: LRM.Section_Declaration
1657 self.match_kw("section")
1658 t_section = self.ct
1659 self.match("STRING")
1660 sec = ast.Section(name = self.ct.value,
1661 location = self.ct.location,
1662 parent = self.section[-1] if self.section else None)
1663 sec.set_ast_link(self.ct)
1664 sec.set_ast_link(t_section)
1665 self.section.append(sec)
1666 self.match("C_BRA")
1667 sec.set_ast_link(self.ct)
1668 while not self.peek("C_KET"):
1669 self.parse_trlc_entry()
1670 self.match("C_KET")
1671 sec.set_ast_link(self.ct)
1672 self.section.pop()
1674 def parse_boolean(self):
1675 # lobster-trace: LRM.Boolean_Values
1676 self.match("KEYWORD")
1677 if self.ct.value in ("true", "false"): 1677 ↛ 1680line 1677 didn't jump to line 1680 because the condition on line 1677 was always true
1678 return ast.Boolean_Literal(self.ct, self.builtin_bool)
1679 else:
1680 self.mh.error(self.ct.location,
1681 "expected boolean literal (true or false)")
1683 def parse_value(self, typ):
1684 # lobster-trace: LRM.Tuple_Syntax_Correct_Form
1685 assert isinstance(typ, ast.Type)
1687 if isinstance(typ, ast.Builtin_Numeric_Type):
1688 # lobster-trace: LRM.Integer_Values
1689 # lobster-trace: LRM.Decimal_Values
1690 if self.peek("OPERATOR") and \
1691 self.nt.value in Parser.ADDING_OPERATOR:
1692 self.match("OPERATOR")
1693 t_op = self.ct
1694 e_op = (ast.Unary_Operator.PLUS
1695 if t_op.value == "+"
1696 else ast.Unary_Operator.MINUS)
1697 t_op.ast_link = e_op
1698 else:
1699 t_op = None
1701 if isinstance(typ, ast.Builtin_Decimal):
1702 self.match("DECIMAL")
1703 rv = ast.Decimal_Literal(self.ct, self.builtin_decimal)
1704 rv.set_ast_link(self.ct)
1705 elif isinstance(typ, ast.Builtin_Integer):
1706 self.match("INTEGER")
1707 rv = ast.Integer_Literal(self.ct, self.builtin_int)
1708 rv.set_ast_link(self.ct)
1709 else:
1710 assert False
1712 if t_op:
1713 rv = ast.Unary_Expression(mh = self.mh,
1714 location = t_op.location,
1715 typ = rv.typ,
1716 operator = e_op,
1717 n_operand = rv)
1719 return rv
1721 elif isinstance(typ, ast.Builtin_Markup_String):
1722 # lobster-trace: LRM.Markup_String_Values
1723 return self.parse_markup_string()
1725 elif isinstance(typ, ast.Builtin_String):
1726 # lobster-trace: LRM.String_Values
1727 self.match("STRING")
1728 rv = ast.String_Literal(self.ct, self.builtin_str)
1729 rv.set_ast_link(self.ct)
1730 return rv
1732 elif isinstance(typ, ast.Builtin_Boolean):
1733 rv = self.parse_boolean()
1734 rv.set_ast_link(self.ct)
1735 return rv
1737 elif isinstance(typ, ast.Array_Type):
1738 self.match("S_BRA")
1739 rv = ast.Array_Aggregate(self.ct.location,
1740 typ)
1741 rv.set_ast_link(self.ct)
1742 while not self.peek("S_KET"):
1743 array_elem = self.parse_value(typ.element_type)
1744 rv.append(array_elem)
1745 if self.peek("COMMA"):
1746 self.match("COMMA")
1747 rv.set_ast_link(self.ct)
1748 elif self.peek("S_KET") or self.nt is None: 1748 ↛ anywhereline 1748 didn't jump anywhere: it always raised an exception.
1749 break
1750 else:
1751 self.mh.error(self.ct.location,
1752 "comma separating array elements is "
1753 "missing",
1754 fatal = False)
1756 self.match("S_KET")
1757 rv.set_ast_link(self.ct)
1759 if len(rv.value) < typ.lower_bound:
1760 self.mh.error(self.ct.location,
1761 "this array requires at least %u elements "
1762 "(only %u provided)" %
1763 (typ.lower_bound,
1764 len(rv.value)),
1765 fatal=False)
1766 if typ.upper_bound and len(rv.value) > typ.upper_bound:
1767 self.mh.error(rv.value[typ.upper_bound].location,
1768 "this array requires at most %u elements "
1769 "(%u provided)" %
1770 (typ.upper_bound,
1771 len(rv.value)),
1772 fatal=False)
1774 return rv
1776 elif isinstance(typ, ast.Enumeration_Type):
1777 enum = self.parse_qualified_name(self.default_scope,
1778 ast.Enumeration_Type)
1779 enum.set_ast_link(self.ct)
1780 if enum != typ:
1781 self.mh.error(self.ct.location,
1782 "expected %s" % typ.name)
1783 self.match("DOT")
1784 enum.set_ast_link(self.ct)
1785 self.match("IDENTIFIER")
1786 lit = enum.literals.lookup(self.mh,
1787 self.ct,
1788 ast.Enumeration_Literal_Spec)
1789 return ast.Enumeration_Literal(self.ct.location,
1790 lit)
1792 elif isinstance(typ, (ast.Record_Type, ast.Union_Type)):
1793 self.match("IDENTIFIER")
1794 t_name = self.ct
1795 if self.peek("DOT"):
1796 self.match("DOT")
1797 t_dot = self.ct
1798 self.match("IDENTIFIER")
1799 the_pkg = self.stab.lookup(self.mh, t_name, ast.Package)
1800 the_pkg.set_ast_link(t_name)
1801 the_pkg.set_ast_link(t_dot)
1802 if not self.cu.is_visible(the_pkg): 1802 ↛ 1803line 1802 didn't jump to line 1803 because the condition on line 1802 was never true
1803 self.mh.error(self.ct.location,
1804 "package must be imported before use")
1805 t_name = self.ct
1806 else:
1807 the_pkg = self.cu.package
1809 rv = ast.Record_Reference(location = t_name.location,
1810 name = t_name.value,
1811 typ = typ,
1812 package = the_pkg)
1813 rv.set_ast_link(t_name)
1815 # We can do an early lookup if the target is known
1816 if the_pkg.symbols.contains(t_name.value):
1817 rv.resolve_references(self.mh)
1819 return rv
1821 elif isinstance(typ, ast.Tuple_Type) and typ.has_separators():
1822 # lobster-trace: LRM.Tuple_Separator_Form
1823 rv = ast.Tuple_Aggregate(self.nt.location, typ)
1825 next_is_optional = False
1826 for n_item in typ.iter_sequence():
1827 if isinstance(n_item, ast.Composite_Component):
1828 if next_is_optional and n_item.optional:
1829 break
1830 value = self.parse_value(n_item.n_typ)
1831 rv.assign(n_item.name, value)
1833 elif n_item.token.kind in ("AT", "COLON", "SEMICOLON"):
1834 if self.peek(n_item.token.kind):
1835 self.match(n_item.token.kind)
1836 n_item.set_ast_link(self.ct)
1837 else:
1838 next_is_optional = True
1840 elif n_item.token.kind == "IDENTIFIER":
1841 if self.peek("IDENTIFIER") and \
1842 self.nt.value == n_item.token.value:
1843 self.match("IDENTIFIER")
1844 n_item.set_ast_link(self.ct)
1845 else:
1846 next_is_optional = True
1848 else:
1849 assert False
1851 return rv
1853 elif isinstance(typ, ast.Tuple_Type) and not typ.has_separators():
1854 # lobster-trace: LRM.Tuple_Generic_Form
1855 self.match("BRA")
1856 rv = ast.Tuple_Aggregate(self.ct.location, typ)
1857 rv.set_ast_link(self.ct)
1859 first = True
1860 for n_field in typ.iter_sequence():
1861 if first:
1862 first = False
1863 else:
1864 self.match("COMMA")
1865 rv.set_ast_link(self.ct)
1866 rv.assign(n_field.name,
1867 self.parse_value(n_field.n_typ))
1869 self.match("KET")
1870 rv.set_ast_link(self.ct)
1871 return rv
1873 else:
1874 self.mh.ice_loc(self.ct.location,
1875 "logic error: unexpected type %s" %
1876 typ.__class__.__name__)
1878 def parse_markup_string(self):
1879 # lobster-trace: LRM.Markup_String_Values
1880 self.match("STRING")
1881 rv = ast.String_Literal(self.ct, self.builtin_mstr)
1882 mpar = Markup_Parser(self, rv)
1883 mpar.parse_all_references()
1884 return rv
1886 def parse_record_object_declaration(self):
1887 # lobster-trace: LRM.Section_Declaration
1888 # lobster-trace: LRM.Record_Object_Declaration
1889 # lobster-trace: LRM.Valid_Record_Types
1890 # lobster-trace: LRM.Valid_Components
1891 # lobster-trace: LRM.Valid_Enumeration_Literals
1892 # lobster-trace: LRM.Mandatory_Components
1893 # lobster-trace: LRM.Evaluation_Of_Checks
1894 # lobster-trace: LRM.Single_Value_Assignment
1896 r_typ = self.parse_qualified_name(self.default_scope,
1897 ast.Record_Type)
1898 r_typ.set_ast_link(self.ct)
1899 # lobster-trace: LRM.Abstract_Types
1900 if r_typ.is_abstract:
1901 self.mh.error(self.ct.location,
1902 "cannot declare object of abstract record type %s" %
1903 r_typ.name)
1905 self.match("IDENTIFIER")
1906 obj = ast.Record_Object(
1907 name = self.ct.value,
1908 location = self.ct.location,
1909 n_typ = r_typ,
1910 section = self.section.copy() if self.section else None,
1911 n_package = self.cu.package)
1912 self.cu.package.symbols.register(self.mh, obj)
1913 obj.set_ast_link(self.ct)
1915 self.match("C_BRA")
1916 obj.set_ast_link(self.ct)
1917 while not self.peek("C_KET"):
1918 self.match("IDENTIFIER")
1919 comp = r_typ.components.lookup(self.mh,
1920 self.ct,
1921 ast.Composite_Component)
1922 if obj.is_component_implicit_null(comp):
1923 self.mh.error(self.ct.location,
1924 "component '%s' already assigned at line %i" %
1925 (comp.name,
1926 obj.field[comp.name].location.line_no))
1927 comp.set_ast_link(self.ct)
1928 if r_typ.is_frozen(comp):
1929 self.mh.error(self.ct.location,
1930 "cannot overwrite frozen component %s" %
1931 comp.name)
1932 self.match("ASSIGN")
1933 comp.set_ast_link(self.ct)
1934 value = self.parse_value(comp.n_typ)
1935 if not self.ct.ast_link:
1936 value.set_ast_link(self.ct)
1937 obj.assign(comp, value)
1939 # Check that each non-optional component has been specified
1940 for comp in r_typ.all_components():
1941 if isinstance(obj.field[comp.name], ast.Implicit_Null):
1942 if r_typ.is_frozen(comp):
1943 obj.assign(comp, r_typ.get_freezing_expression(comp))
1944 elif not comp.optional:
1945 self.mh.error(
1946 obj.location,
1947 "required component %s (see %s) is not defined" %
1948 (comp.name,
1949 self.mh.cross_file_reference(comp.location)))
1951 self.match("C_KET")
1952 obj.set_ast_link(self.ct)
1954 return obj
1956 def parse_trlc_entry(self):
1957 # lobster-trace: LRM.TRLC_File
1958 if self.peek_kw("section"):
1959 self.parse_section_declaration()
1960 else:
1961 self.cu.add_item(self.parse_record_object_declaration())
1963 def parse_preamble(self, kind):
1964 assert kind in ("rsl", "trlc")
1965 # lobster-trace: LRM.Layout
1966 # lobster-trace: LRM.Preamble
1968 # First, parse package indication, declaring the package if
1969 # needed
1970 self.match_kw("package")
1971 t_pkg = self.ct
1972 self.match("IDENTIFIER")
1974 if kind == "rsl":
1975 declare_package = True
1976 else:
1977 # lobster-trace: LRM.Late_Package_Declarations
1978 declare_package = not self.stab.contains(self.ct.value)
1980 if declare_package:
1981 # lobster-trace: LRM.Package_Declaration
1982 pkg = ast.Package(name = self.ct.value,
1983 location = self.ct.location,
1984 builtin_stab = self.stab,
1985 declared_late = kind == "trlc")
1986 self.stab.register(self.mh, pkg)
1987 else:
1988 pkg = self.stab.lookup(self.mh, self.ct, ast.Package)
1990 pkg.set_ast_link(t_pkg)
1991 pkg.set_ast_link(self.ct)
1993 # lobster-trace: LRM.Current_Package
1994 self.cu.set_package(pkg)
1996 self.default_scope.push(self.cu.package.symbols)
1998 # Second, parse import list (but don't resolve names yet)
1999 # lobster-trace: LRM.Import_Visibility
2000 if kind != "check": 2000 ↛ exitline 2000 didn't return from function 'parse_preamble' because the condition on line 2000 was always true
2001 while self.peek_kw("import"):
2002 self.match_kw("import")
2003 pkg.set_ast_link(self.ct)
2004 self.match("IDENTIFIER")
2005 self.cu.add_import(self.mh, self.ct)
2007 def parse_rsl_file(self):
2008 # lobster-trace: LRM.RSL_File
2009 assert self.cu.package is not None
2011 ok = True
2012 while not self.peek_eof():
2013 try:
2014 if self.peek_kw("checks"):
2015 self.cu.add_item(self.parse_check_block())
2016 else:
2017 self.cu.add_item(self.parse_type_declaration())
2018 except TRLC_Error as err:
2019 if not self.error_recovery or err.kind == "lex error": 2019 ↛ 2020line 2019 didn't jump to line 2020 because the condition on line 2019 was never true
2020 raise
2022 ok = False
2024 # Recovery strategy is to scan until we get the next
2025 # relevant keyword
2026 self.skip_until_newline()
2027 while not self.peek_eof():
2028 if self.peek_kw("checks") or \
2029 self.peek_kw("type") or \
2030 self.peek_kw("abstract") or \
2031 self.peek_kw("final") or \
2032 self.peek_kw("tuple") or \
2033 self.peek_kw("enum"):
2034 break
2035 self.advance()
2036 self.skip_until_newline()
2038 self.match_eof()
2040 for tok in self.lexer.tokens:
2041 if tok.kind == "COMMENT":
2042 self.cu.package.set_ast_link(tok)
2044 return ok
2046 def parse_trlc_file(self):
2047 # lobster-trace: LRM.TRLC_File
2048 assert self.cu.package is not None
2050 ok = True
2052 while self.peek_kw("section") or self.peek("IDENTIFIER"):
2053 try:
2054 self.parse_trlc_entry()
2055 except TRLC_Error as err:
2056 if not self.error_recovery or err.kind == "lex error": 2056 ↛ 2057line 2056 didn't jump to line 2057 because the condition on line 2056 was never true
2057 raise
2059 ok = False
2061 # Recovery strategy is to keep going until we find an
2062 # identifier that is a package or type, or section, or
2063 # EOF
2064 self.skip_until_newline()
2065 while not self.peek_eof():
2066 if self.peek_kw("section"): 2066 ↛ 2067line 2066 didn't jump to line 2067 because the condition on line 2066 was never true
2067 break
2068 elif not self.peek("IDENTIFIER"):
2069 pass
2070 elif self.stab.contains(self.nt.value): 2070 ↛ 2071line 2070 didn't jump to line 2071 because the condition on line 2070 was never true
2071 n_sym = self.stab.lookup_assuming(self.mh,
2072 self.nt.value)
2073 if isinstance(n_sym, ast.Package):
2074 break
2075 elif self.cu.package.symbols.contains(self.nt.value):
2076 n_sym = self.cu.package.symbols.lookup_assuming(
2077 self.mh,
2078 self.nt.value)
2079 if isinstance(n_sym, ast.Record_Type):
2080 break
2081 self.advance()
2082 self.skip_until_newline()
2084 self.match_eof()
2086 for tok in self.lexer.tokens:
2087 if tok.kind == "COMMENT":
2088 self.cu.package.set_ast_link(tok)
2090 return ok