Coverage for trlc/parser.py: 96%
1079 statements
« prev ^ index » next coverage.py v7.7.1, created at 2025-03-27 00:52 +0000
« prev ^ index » next coverage.py v7.7.1, created at 2025-03-27 00:52 +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#
6# This file is part of the TRLC Python Reference Implementation.
7#
8# TRLC is free software: you can redistribute it and/or modify it
9# under the terms of the GNU General Public License as published by
10# the Free Software Foundation, either version 3 of the License, or
11# (at your option) any later version.
12#
13# TRLC is distributed in the hope that it will be useful, but WITHOUT
14# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
15# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
16# License for more details.
17#
18# You should have received a copy of the GNU General Public License
19# along with TRLC. If not, see <https://www.gnu.org/licenses/>.
21import re
23from trlc.nested import Nested_Lexer
24from trlc.lexer import Token_Base, Token, Lexer_Base, TRLC_Lexer
25from trlc.errors import Message_Handler, TRLC_Error
26from trlc import ast
29class Markup_Token(Token_Base):
30 # lobster-trace: LRM.Markup_String_Format
32 KIND = {
33 "CHARACTER" : "character",
34 "REFLIST_BEGIN" : "[[",
35 "REFLIST_END" : "]]",
36 "REFLIST_COMMA" : ",",
37 "REFLIST_DOT" : ".",
38 "REFLIST_IDENTIFIER" : "identifier",
39 }
41 def __init__(self, location, kind, value):
42 super().__init__(location, kind, value)
43 assert isinstance(value, str)
46class Markup_Lexer(Nested_Lexer):
47 def __init__(self, mh, literal):
48 super().__init__(mh, literal)
50 self.in_reflist = False
52 def file_location(self):
53 return self.origin_location
55 def token(self):
56 # lobster-trace: LRM.Markup_String_Errors
58 if self.in_reflist:
59 self.skip_whitespace()
60 else:
61 self.advance()
62 if self.cc is None:
63 return None
65 start_pos = self.lexpos
66 start_line = self.line_no
67 start_col = self.col_no
69 if self.cc == "[" and self.nc == "[":
70 kind = "REFLIST_BEGIN"
71 self.advance()
72 if self.in_reflist:
73 self.mh.lex_error(self.source_location(start_line,
74 start_col,
75 start_pos,
76 start_pos + 1),
77 "cannot nest reference lists")
78 else:
79 self.in_reflist = True
81 elif self.cc == "]" and self.nc == "]":
82 kind = "REFLIST_END"
83 self.advance()
84 if self.in_reflist:
85 self.in_reflist = False
86 else:
87 self.mh.lex_error(self.source_location(start_line,
88 start_col,
89 start_pos,
90 start_pos + 1),
91 "opening [[ for this ]] found")
93 elif not self.in_reflist:
94 kind = "CHARACTER"
96 elif self.cc == ",":
97 kind = "REFLIST_COMMA"
99 elif self.cc == ".":
100 kind = "REFLIST_DOT"
102 elif self.is_alpha(self.cc): 102 ↛ 109line 102 didn't jump to line 109 because the condition on line 102 was always true
103 kind = "REFLIST_IDENTIFIER"
104 while self.nc and (self.is_alnum(self.nc) or
105 self.nc == "_"):
106 self.advance()
108 else:
109 self.mh.lex_error(self.source_location(start_line,
110 start_col,
111 start_pos,
112 start_pos),
113 "unexpected character '%s'" % self.cc)
115 loc = self.source_location(start_line,
116 start_col,
117 start_pos,
118 self.lexpos)
120 # pylint: disable=possibly-used-before-assignment
121 return Markup_Token(loc,
122 kind,
123 self.content[start_pos:self.lexpos + 1])
126class Parser_Base:
127 def __init__(self, mh, lexer, eoc_name, token_map, keywords):
128 assert isinstance(mh, Message_Handler)
129 assert isinstance(lexer, Lexer_Base)
130 assert isinstance(eoc_name, str)
131 assert isinstance(token_map, dict)
132 assert isinstance(keywords, frozenset)
133 self.mh = mh
134 self.lexer = lexer
136 self.eoc_name = eoc_name
137 self.language_tokens = token_map
138 self.language_keywords = keywords
140 self.ct = None
141 self.nt = None
142 self.advance()
144 def advance(self):
145 # lobster-trace: LRM.Comments
146 self.ct = self.nt
147 while True:
148 self.nt = self.lexer.token()
149 if self.nt is None or self.nt.kind != "COMMENT":
150 break
152 def skip_until_newline(self):
153 if self.ct is None: 153 ↛ 154line 153 didn't jump to line 154 because the condition on line 153 was never true
154 return
155 current_line = self.ct.location.line_no
156 while self.nt and self.nt.location.line_no == current_line:
157 self.advance()
159 def peek(self, kind):
160 assert kind in self.language_tokens, \
161 "%s is not a valid token" % kind
162 return self.nt is not None and self.nt.kind == kind
164 def peek_eof(self):
165 return self.nt is None
167 def peek_kw(self, value):
168 assert value in self.language_keywords, \
169 "%s is not a valid keyword" % value
170 return self.peek("KEYWORD") and self.nt.value == value
172 def match(self, kind):
173 # lobster-trace: LRM.Matching_Value_Types
175 assert kind in self.language_tokens, \
176 "%s is not a valid token" % kind
177 if self.nt is None:
178 if self.ct is None: 178 ↛ 179line 178 didn't jump to line 179 because the condition on line 178 was never true
179 self.mh.error(self.lexer.file_location(),
180 "expected %s, encountered %s instead" %
181 (self.language_tokens[kind], self.eoc_name))
182 else:
183 self.mh.error(self.ct.location,
184 "expected %s, encountered %s instead" %
185 (self.language_tokens[kind], self.eoc_name))
186 elif self.nt.kind != kind:
187 self.mh.error(self.nt.location,
188 "expected %s, encountered %s instead" %
189 (self.language_tokens[kind],
190 self.language_tokens[self.nt.kind]))
191 self.advance()
193 def match_eof(self):
194 if self.nt is not None: 194 ↛ 195line 194 didn't jump to line 195 because the condition on line 194 was never true
195 self.mh.error(self.nt.location,
196 "expected %s, encountered %s instead" %
197 (self.eoc_name,
198 self.language_tokens[self.nt.kind]))
200 def match_kw(self, value):
201 assert value in self.language_keywords, \
202 "%s is not a valid keyword" % value
203 if self.nt is None:
204 if self.ct is None: 204 ↛ 209line 204 didn't jump to line 209 because the condition on line 204 was always true
205 self.mh.error(self.lexer.file_location(),
206 "expected keyword %s, encountered %s instead" %
207 (value, self.eoc_name))
208 else:
209 self.mh.error(self.ct.location,
210 "expected keyword %s, encountered %s instead" %
211 (value, self.eoc_name))
212 elif self.nt.kind != "KEYWORD":
213 self.mh.error(self.nt.location,
214 "expected keyword %s, encountered %s instead" %
215 (value,
216 self.language_tokens[self.nt.kind]))
217 elif self.nt.value != value:
218 self.mh.error(self.nt.location,
219 "expected keyword %s,"
220 " encountered keyword %s instead" %
221 (value, self.nt.value))
222 self.advance()
225class Markup_Parser(Parser_Base):
226 def __init__(self, parent, literal):
227 assert isinstance(parent, Parser)
228 super().__init__(parent.mh, Markup_Lexer(parent.mh, literal),
229 eoc_name = "end-of-string",
230 token_map = Markup_Token.KIND,
231 keywords = frozenset())
232 self.parent = parent
233 self.references = literal.references
235 def parse_all_references(self):
236 while self.nt:
237 if self.peek("CHARACTER"):
238 self.advance()
239 else:
240 self.parse_ref_list()
241 self.match_eof()
242 return self.references
244 def parse_ref_list(self):
245 self.match("REFLIST_BEGIN")
246 self.parse_qualified_name()
247 while self.peek("REFLIST_COMMA"):
248 self.match("REFLIST_COMMA")
249 self.parse_qualified_name()
250 self.match("REFLIST_END")
252 def parse_qualified_name(self):
253 # lobster-trace: LRM.Qualified_Name
254 # lobster-trace: LRM.Valid_Qualifier
255 # lobster-trace: LRM.Valid_Name
256 # lobster-trace: LRM.Markup_String_Resolution
257 # lobster-trace: LRM.Markup_String_Types
259 self.match("REFLIST_IDENTIFIER")
260 if self.peek("REFLIST_DOT"):
261 package = self.parent.stab.lookup_direct(
262 mh = self.mh,
263 name = self.ct.value,
264 error_location = self.ct.location,
265 required_subclass = ast.Package)
266 if not self.parent.cu.is_visible(package):
267 self.mh.error(self.ct.location,
268 "package must be imported before use")
270 self.match("REFLIST_DOT")
271 self.match("REFLIST_IDENTIFIER")
272 else:
273 package = self.parent.cu.package
275 ref = ast.Record_Reference(location = self.ct.location,
276 name = self.ct.value,
277 typ = None,
278 package = package)
279 self.references.append(ref)
282class Parser(Parser_Base):
283 COMPARISON_OPERATOR = ("==", "!=", "<", "<=", ">", ">=")
284 ADDING_OPERATOR = ("+", "-")
285 MULTIPLYING_OPERATOR = ("*", "/", "%")
287 def __init__(self,
288 mh,
289 stab,
290 file_name,
291 lint_mode,
292 error_recovery,
293 primary_file=True,
294 lexer=None):
295 assert isinstance(mh, Message_Handler)
296 assert isinstance(stab, ast.Symbol_Table)
297 assert isinstance(file_name, str)
298 assert isinstance(lint_mode, bool)
299 assert isinstance(error_recovery, bool)
300 assert isinstance(primary_file, bool)
301 assert isinstance(lexer, TRLC_Lexer) or lexer is None
302 if lexer: 302 ↛ 308line 302 didn't jump to line 308 because the condition on line 302 was always true
303 super().__init__(mh, lexer,
304 eoc_name = "end-of-file",
305 token_map = Token.KIND,
306 keywords = TRLC_Lexer.KEYWORDS)
307 else:
308 super().__init__(mh, TRLC_Lexer(mh, file_name),
309 eoc_name = "end-of-file",
310 token_map = Token.KIND,
311 keywords = TRLC_Lexer.KEYWORDS)
313 self.lint_mode = lint_mode
314 self.error_recovery = error_recovery
316 self.stab = stab
317 self.cu = ast.Compilation_Unit(file_name)
319 self.primary = primary_file
320 self.secondary = False
321 # Controls if the file is actually fully parsed: primary means
322 # it was selected on the command-line and secondary means it
323 # was selected by dependency analysis.
325 self.builtin_bool = stab.lookup_assuming(self.mh, "Boolean")
326 self.builtin_int = stab.lookup_assuming(self.mh, "Integer")
327 self.builtin_decimal = stab.lookup_assuming(self.mh, "Decimal")
328 self.builtin_str = stab.lookup_assuming(self.mh, "String")
329 self.builtin_mstr = stab.lookup_assuming(self.mh, "Markup_String")
331 self.section = []
332 self.default_scope = ast.Scope()
333 self.default_scope.push(self.stab)
335 def parse_described_name(self):
336 # lobster-trace: LRM.Described_Names
337 # lobster-trace: LRM.Described_Name_Description
338 self.match("IDENTIFIER")
339 name = self.ct
341 if self.peek("STRING"):
342 self.match("STRING")
343 t_descr = self.ct
344 return name, t_descr.value, t_descr
345 else:
346 return name, None, None
348 def parse_qualified_name(self,
349 scope,
350 required_subclass=None,
351 match_ident=True):
352 # lobster-trace: LRM.Qualified_Name
353 # lobster-trace: LRM.Valid_Qualifier
354 # lobster-trace: LRM.Valid_Name
355 assert isinstance(scope, ast.Scope)
356 assert required_subclass is None or isinstance(required_subclass, type)
357 assert isinstance(match_ident, bool)
359 if match_ident:
360 self.match("IDENTIFIER")
361 sym = scope.lookup(self.mh, self.ct)
362 sym.set_ast_link(self.ct)
364 if isinstance(sym, ast.Package):
365 if not self.cu.is_visible(sym):
366 self.mh.error(self.ct.location,
367 "package must be imported before use")
368 self.match("DOT")
369 sym.set_ast_link(self.ct)
370 self.match("IDENTIFIER")
371 return sym.symbols.lookup(self.mh, self.ct, required_subclass)
372 else:
373 # Easiest way to generate the correct error message
374 return scope.lookup(self.mh, self.ct, required_subclass)
376 def parse_type_declaration(self):
377 # lobster-trace: LRM.Type_Declarations
378 if self.peek_kw("enum"):
379 n_item = self.parse_enum_declaration()
380 elif self.peek_kw("tuple"):
381 n_item = self.parse_tuple_declaration()
382 else:
383 n_item = self.parse_record_declaration()
384 assert isinstance(n_item, ast.Concrete_Type)
385 return n_item
387 def parse_enum_declaration(self):
388 # lobster-trace: LRM.Enumeration_Declaration
389 self.match_kw("enum")
390 t_enum = self.ct
391 name, description, t_description = self.parse_described_name()
393 enum = ast.Enumeration_Type(name = name.value,
394 description = description,
395 location = name.location,
396 package = self.cu.package)
397 self.cu.package.symbols.register(self.mh, enum)
398 enum.set_ast_link(t_enum)
399 enum.set_ast_link(name)
400 if t_description:
401 enum.set_ast_link(t_description)
403 self.match("C_BRA")
404 enum.set_ast_link(self.ct)
405 empty = True
406 while not self.peek("C_KET"):
407 name, description, t_description = self.parse_described_name()
408 lit = ast.Enumeration_Literal_Spec(name = name.value,
409 description = description,
410 location = name.location,
411 enum = enum)
412 lit.set_ast_link(name)
413 if t_description:
414 lit.set_ast_link(self.ct)
415 empty = False
416 enum.literals.register(self.mh, lit)
417 self.match("C_KET")
418 enum.set_ast_link(self.ct)
420 if empty:
421 # lobster-trace: LRM.No_Empty_Enumerations
422 self.mh.error(enum.location,
423 "empty enumerations are not permitted")
425 return enum
427 def parse_tuple_field(self,
428 n_tuple,
429 optional_allowed,
430 optional_reason,
431 optional_required):
432 assert isinstance(n_tuple, ast.Tuple_Type)
433 assert isinstance(optional_allowed, bool)
434 assert isinstance(optional_reason, str)
435 assert isinstance(optional_required, bool)
436 assert optional_allowed or not optional_required
438 field_name, field_description, t_descr = self.parse_described_name()
440 if optional_required or self.peek_kw("optional"):
441 self.match_kw("optional")
442 t_optional = self.ct
443 field_is_optional = True
444 if not optional_allowed:
445 self.mh.error(self.ct.location, optional_reason)
446 else:
447 field_is_optional = False
448 t_optional = None
450 # lobster-trace: LRM.Tuple_Field_Types
451 field_type = self.parse_qualified_name(self.default_scope,
452 ast.Type)
453 comp = ast.Composite_Component(name = field_name.value,
454 description = field_description,
455 location = field_name.location,
456 member_of = n_tuple,
457 n_typ = field_type,
458 optional = field_is_optional)
459 comp.set_ast_link(field_name)
460 if t_descr:
461 comp.set_ast_link(t_descr)
462 if field_is_optional:
463 comp.set_ast_link(t_optional)
465 return comp
467 def parse_tuple_declaration(self):
468 # lobster-trace: LRM.Tuple_Declaration
469 self.match_kw("tuple")
470 t_tuple = self.ct
471 name, description, t_descr = self.parse_described_name()
473 n_tuple = ast.Tuple_Type(name = name.value,
474 description = description,
475 location = name.location,
476 package = self.cu.package)
478 n_tuple.set_ast_link(t_tuple)
479 n_tuple.set_ast_link(name)
480 if t_descr:
481 n_tuple.set_ast_link(t_descr)
482 self.match("C_BRA")
483 n_tuple.set_ast_link(self.ct)
485 n_field = self.parse_tuple_field(
486 n_tuple,
487 optional_allowed = False,
488 optional_reason = "first field may not be optional",
489 optional_required = False)
490 n_tuple.components.register(self.mh, n_field)
492 has_separators = False
493 optional_required = False
494 separator_allowed = True
496 while self.peek_kw("separator") or self.peek("IDENTIFIER"):
497 if has_separators or self.peek_kw("separator"):
498 has_separators = True
499 self.match_kw("separator")
500 t_sep = self.ct
501 if not separator_allowed:
502 # lobster-trace: LRM.Tuple_Separators_All_Or_None
503 self.mh.error(self.ct.location,
504 "either all fields must be separated,"
505 " or none")
506 if self.peek("IDENTIFIER") or \ 506 ↛ 518line 506 didn't jump to line 518 because the condition on line 506 was always true
507 self.peek("AT") or \
508 self.peek("COLON") or \
509 self.peek("SEMICOLON"):
510 self.advance()
511 sep = ast.Separator(self.ct)
512 sep.set_ast_link(t_sep)
513 sep.set_ast_link(self.ct)
514 n_tuple.add_separator(sep)
515 else:
516 separator_allowed = False
517 # lobster-trace: LRM.Tuple_Optional_Requires_Separators
518 n_field = self.parse_tuple_field(
519 n_tuple,
520 optional_allowed = has_separators,
521 optional_reason = ("optional only permitted in tuples"
522 " with separators"),
523 optional_required = optional_required)
524 n_tuple.components.register(self.mh, n_field)
525 # lobster-trace: LRM.Tuple_Optional_Fields
526 optional_required |= n_field.optional
528 self.match("C_KET")
529 n_tuple.set_ast_link(self.ct)
531 # Final check to ban tuples with separators containing other
532 # tuples.
533 if has_separators:
534 # lobster-trace: LRM.Restricted_Tuple_Nesting
535 for n_field in n_tuple.components.values():
536 if isinstance(n_field.n_typ, ast.Tuple_Type) and \
537 n_field.n_typ.has_separators():
538 self.mh.error(
539 n_field.location,
540 "tuple type %s, which contains separators,"
541 " may not contain another tuple with separators"
542 % n_tuple.name)
544 # Late registration to avoid recursion in tuples
545 # lobster-trace: LRM.Tuple_Field_Types
546 self.cu.package.symbols.register(self.mh, n_tuple)
548 return n_tuple
550 def parse_record_component(self, n_record):
551 assert isinstance(n_record, ast.Record_Type)
553 c_name, c_descr, t_descr = self.parse_described_name()
554 t_optional = None
555 c_optional = False
556 if self.peek_kw("optional"):
557 self.match_kw("optional")
558 t_optional = self.ct
559 c_optional = True
560 c_typ = self.parse_qualified_name(self.default_scope,
561 ast.Type)
562 c_typ.set_ast_link(self.ct)
564 if self.peek("S_BRA"):
565 self.match("S_BRA")
566 t_s_bra = self.ct
567 self.match("INTEGER")
568 t_lo = self.ct
569 a_lo = self.ct.value
570 loc_lo = self.ct.location
571 self.match("RANGE")
572 t_range = self.ct
573 a_loc = self.ct.location
574 a_hi = None
575 if self.peek("INTEGER"):
576 self.match("INTEGER")
577 a_hi = self.ct.value
578 elif self.peek("OPERATOR") and self.nt.value == "*": 578 ↛ 581line 578 didn't jump to line 581 because the condition on line 578 was always true
579 self.match("OPERATOR")
580 else:
581 self.mh.error(self.nt.location,
582 "expected INTEGER or * for upper bound")
583 t_hi = self.ct
584 loc_hi = self.ct.location
585 self.match("S_KET")
586 t_s_ket = self.ct
587 c_typ = ast.Array_Type(location = a_loc,
588 element_type = c_typ,
589 lower_bound = a_lo,
590 upper_bound = a_hi,
591 loc_lower = loc_lo,
592 loc_upper = loc_hi)
593 c_typ.set_ast_link(t_s_bra)
594 c_typ.set_ast_link(t_lo)
595 c_typ.set_ast_link(t_range)
596 c_typ.set_ast_link(t_hi)
597 c_typ.set_ast_link(t_s_ket)
599 c_comp = ast.Composite_Component(name = c_name.value,
600 description = c_descr,
601 location = c_name.location,
602 member_of = n_record,
603 n_typ = c_typ,
604 optional = c_optional)
605 c_comp.set_ast_link(c_name)
606 if t_descr:
607 c_comp.set_ast_link(t_descr)
608 if c_optional:
609 c_comp.set_ast_link(t_optional)
611 return c_comp
613 def parse_record_declaration(self):
614 t_abstract = None
615 t_final = None
616 is_abstract = False
617 is_final = False
618 if self.peek_kw("abstract"):
619 self.match_kw("abstract")
620 t_abstract = self.ct
621 is_abstract = True
622 elif self.peek_kw("final"):
623 self.match_kw("final")
624 t_final = self.ct
625 is_final = True
627 self.match_kw("type")
628 t_type = self.ct
629 name, description, t_description = self.parse_described_name()
631 if self.peek_kw("extends"):
632 self.match_kw("extends")
633 t_extends = self.ct
634 root_record = self.parse_qualified_name(self.default_scope,
635 ast.Record_Type)
636 root_record.set_ast_link(t_extends)
637 root_record.set_ast_link(self.ct)
638 else:
639 root_record = None
641 if self.lint_mode and \
642 root_record and root_record.is_final and \
643 not is_final:
644 self.mh.check(name.location,
645 "consider clarifying that this record is final",
646 "clarify_final",
647 ("Parent record %s is final, making this record\n"
648 "also final. Marking it explicitly as final\n"
649 "clarifies this to casual readers." %
650 root_record.fully_qualified_name()))
652 record = ast.Record_Type(name = name.value,
653 description = description,
654 location = name.location,
655 package = self.cu.package,
656 n_parent = root_record,
657 is_abstract = is_abstract)
658 self.cu.package.symbols.register(self.mh, record)
659 if is_abstract:
660 record.set_ast_link(t_abstract)
661 if is_final:
662 record.set_ast_link(t_final)
663 record.set_ast_link(t_type)
664 record.set_ast_link(name)
665 if t_description:
666 record.set_ast_link(t_description)
668 self.match("C_BRA")
669 record.set_ast_link(self.ct)
670 while not self.peek("C_KET"):
671 if self.peek_kw("freeze"):
672 self.match_kw("freeze")
673 t_freeze = self.ct
674 self.match("IDENTIFIER")
675 n_comp = record.components.lookup(self.mh,
676 self.ct,
677 ast.Composite_Component)
678 if record.is_frozen(n_comp):
679 n_value = record.get_freezing_expression(n_comp)
680 self.mh.error(
681 self.ct.location,
682 "duplicate freezing of %s, previously frozen at %s" %
683 (n_comp.name,
684 self.mh.cross_file_reference(n_value.location)))
685 n_comp.set_ast_link(t_freeze)
686 n_comp.set_ast_link(self.ct)
687 self.match("ASSIGN")
688 n_comp.set_ast_link(self.ct)
689 n_value = self.parse_value(n_comp.n_typ)
690 n_value.set_ast_link(self.ct)
692 record.frozen[n_comp.name] = n_value
694 else:
695 n_comp = self.parse_record_component(record)
696 if record.is_final:
697 self.mh.error(n_comp.location,
698 "cannot declare new components in"
699 " final record type")
700 else:
701 record.components.register(self.mh, n_comp)
703 self.match("C_KET")
704 record.set_ast_link(self.ct)
706 # Finally mark record final if applicable
707 if is_final:
708 record.is_final = True
710 return record
712 def parse_expression(self, scope):
713 # lobster-trace: LRM.Expression
714 assert isinstance(scope, ast.Scope)
716 n_lhs = self.parse_relation(scope)
718 if self.peek_kw("and"):
719 while self.peek_kw("and"):
720 self.match_kw("and")
721 t_op = self.ct
722 a_op = ast.Binary_Operator.LOGICAL_AND
723 t_op.ast_link = a_op
724 n_rhs = self.parse_relation(scope)
725 n_lhs = ast.Binary_Expression(
726 mh = self.mh,
727 location = t_op.location,
728 typ = self.builtin_bool,
729 operator = a_op,
730 n_lhs = n_lhs,
731 n_rhs = n_rhs)
733 elif self.peek_kw("or"):
734 while self.peek_kw("or"):
735 self.match_kw("or")
736 t_op = self.ct
737 a_op = ast.Binary_Operator.LOGICAL_OR
738 t_op.ast_link = a_op
739 n_rhs = self.parse_relation(scope)
740 n_lhs = ast.Binary_Expression(
741 mh = self.mh,
742 location = t_op.location,
743 typ = self.builtin_bool,
744 operator = a_op,
745 n_lhs = n_lhs,
746 n_rhs = n_rhs)
748 elif self.peek_kw("xor"):
749 self.match_kw("xor")
750 t_op = self.ct
751 a_op = ast.Binary_Operator.LOGICAL_XOR
752 t_op.ast_link = a_op
753 n_rhs = self.parse_relation(scope)
754 n_lhs = ast.Binary_Expression(
755 mh = self.mh,
756 location = t_op.location,
757 typ = self.builtin_bool,
758 operator = a_op,
759 n_lhs = n_lhs,
760 n_rhs = n_rhs)
762 elif self.peek_kw("implies"):
763 self.match_kw("implies")
764 t_op = self.ct
765 a_op = ast.Binary_Operator.LOGICAL_IMPLIES
766 t_op.ast_link = a_op
767 n_rhs = self.parse_relation(scope)
768 n_lhs = ast.Binary_Expression(
769 mh = self.mh,
770 location = t_op.location,
771 typ = self.builtin_bool,
772 operator = a_op,
773 n_lhs = n_lhs,
774 n_rhs = n_rhs)
776 return n_lhs
778 def parse_relation(self, scope):
779 # lobster-trace: LRM.Relation
780 # lobster-trace: LRM.Operators
781 assert isinstance(scope, ast.Scope)
782 relop_mapping = {"==" : ast.Binary_Operator.COMP_EQ,
783 "!=" : ast.Binary_Operator.COMP_NEQ,
784 "<" : ast.Binary_Operator.COMP_LT,
785 "<=" : ast.Binary_Operator.COMP_LEQ,
786 ">" : ast.Binary_Operator.COMP_GT,
787 ">=" : ast.Binary_Operator.COMP_GEQ}
788 assert set(relop_mapping) == set(Parser.COMPARISON_OPERATOR)
790 n_lhs = self.parse_simple_expression(scope)
792 if self.peek("OPERATOR") and \
793 self.nt.value in Parser.COMPARISON_OPERATOR:
794 self.match("OPERATOR")
795 t_op = self.ct
796 a_op = relop_mapping[t_op.value]
797 t_op.ast_link = a_op
798 n_rhs = self.parse_simple_expression(scope)
799 return 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("not") or self.peek_kw("in"):
808 if self.peek_kw("not"):
809 self.match_kw("not")
810 t_not = self.ct
811 else:
812 t_not = None
814 self.match_kw("in")
815 t_in = self.ct
817 n_a = self.parse_simple_expression(scope)
818 t_n_a = self.ct
819 if self.peek("RANGE"):
820 self.match("RANGE")
821 t_range = self.ct
822 n_b = self.parse_simple_expression(scope)
823 n_b.set_ast_link(self.ct)
824 n_a.set_ast_link(t_n_a)
825 rv = ast.Range_Test(
826 mh = self.mh,
827 location = t_in.location,
828 typ = self.builtin_bool,
829 n_lhs = n_lhs,
830 n_lower = n_a,
831 n_upper = n_b)
832 rv.set_ast_link(t_range)
833 rv.set_ast_link(t_in)
835 elif isinstance(n_a.typ, ast.Builtin_String):
836 rv = ast.Binary_Expression(
837 mh = self.mh,
838 location = t_in.location,
839 typ = self.builtin_bool,
840 operator = ast.Binary_Operator.STRING_CONTAINS,
841 n_lhs = n_lhs,
842 n_rhs = n_a)
843 rv.set_ast_link(t_in)
845 elif isinstance(n_a.typ, ast.Array_Type): 845 ↛ 857line 845 didn't jump to line 857 because the condition on line 845 was always true
846 a_op = ast.Binary_Operator.ARRAY_CONTAINS
847 t_in.ast_link = a_op
848 rv = ast.Binary_Expression(
849 mh = self.mh,
850 location = t_in.location,
851 typ = self.builtin_bool,
852 operator = a_op,
853 n_lhs = n_lhs,
854 n_rhs = n_a)
856 else:
857 self.mh.error(
858 n_a.location,
859 "membership test only defined for Strings and Arrays,"
860 " not for %s" % n_a.typ.name)
862 if t_not is not None:
863 a_unary_op = ast.Unary_Operator.LOGICAL_NOT
864 t_not.ast_link = a_unary_op
865 rv = ast.Unary_Expression(
866 mh = self.mh,
867 location = t_not.location,
868 typ = self.builtin_bool,
869 operator = a_unary_op,
870 n_operand = rv)
872 return rv
874 else:
875 return n_lhs
877 def parse_simple_expression(self, scope):
878 # lobster-trace: LRM.Simple_Expression
879 # lobster-trace: LRM.Operators
880 # lobster-trace: LRM.Unary_Minus_Parsing
881 assert isinstance(scope, ast.Scope)
882 un_add_map = {"+" : ast.Unary_Operator.PLUS,
883 "-" : ast.Unary_Operator.MINUS}
884 bin_add_map = {"+" : ast.Binary_Operator.PLUS,
885 "-" : ast.Binary_Operator.MINUS}
886 assert set(un_add_map) == set(Parser.ADDING_OPERATOR)
887 assert set(bin_add_map) == set(Parser.ADDING_OPERATOR)
889 if self.peek("OPERATOR") and \
890 self.nt.value in Parser.ADDING_OPERATOR:
891 self.match("OPERATOR")
892 t_unary = self.ct
893 a_unary = un_add_map[t_unary.value]
894 t_unary.ast_link = a_unary
895 has_explicit_brackets = self.peek("BRA")
896 else:
897 t_unary = None
899 n_lhs = self.parse_term(scope)
900 if t_unary:
901 # pylint: disable=possibly-used-before-assignment
902 if self.lint_mode and \
903 isinstance(n_lhs, ast.Binary_Expression) and \
904 not has_explicit_brackets:
905 self.mh.check(t_unary.location,
906 "expression means -(%s), place explicit "
907 "brackets to clarify intent" %
908 n_lhs.to_string(),
909 "unary_minus_precedence")
911 n_lhs = ast.Unary_Expression(
912 mh = self.mh,
913 location = t_unary.location,
914 typ = n_lhs.typ,
915 operator = a_unary,
916 n_operand = n_lhs)
918 if isinstance(n_lhs.typ, ast.Builtin_String):
919 rtyp = self.builtin_str
920 else:
921 rtyp = n_lhs.typ
923 while self.peek("OPERATOR") and \
924 self.nt.value in Parser.ADDING_OPERATOR:
925 self.match("OPERATOR")
926 t_op = self.ct
927 a_op = bin_add_map[t_op.value]
928 t_op.ast_link = a_op
929 n_rhs = self.parse_term(scope)
930 n_lhs = ast.Binary_Expression(
931 mh = self.mh,
932 location = t_op.location,
933 typ = rtyp,
934 operator = a_op,
935 n_lhs = n_lhs,
936 n_rhs = n_rhs)
938 return n_lhs
940 def parse_term(self, scope):
941 # lobster-trace: LRM.Term
942 # lobster-trace: LRM.Operators
943 assert isinstance(scope, ast.Scope)
944 mul_map = {"*" : ast.Binary_Operator.TIMES,
945 "/" : ast.Binary_Operator.DIVIDE,
946 "%" : ast.Binary_Operator.REMAINDER}
947 assert set(mul_map) == set(Parser.MULTIPLYING_OPERATOR)
949 n_lhs = self.parse_factor(scope)
950 while self.peek("OPERATOR") and \
951 self.nt.value in Parser.MULTIPLYING_OPERATOR:
952 self.match("OPERATOR")
953 t_op = self.ct
954 a_op = mul_map[t_op.value]
955 t_op.ast_link = a_op
956 n_rhs = self.parse_factor(scope)
957 n_lhs = ast.Binary_Expression(
958 mh = self.mh,
959 location = t_op.location,
960 typ = n_lhs.typ,
961 operator = a_op,
962 n_lhs = n_lhs,
963 n_rhs = n_rhs)
965 return n_lhs
967 def parse_factor(self, scope):
968 # lobster-trace: LRM.Factor
969 assert isinstance(scope, ast.Scope)
971 if self.peek_kw("not"):
972 self.match_kw("not")
973 t_op = self.ct
974 n_operand = self.parse_primary(scope)
975 a_not = ast.Unary_Operator.LOGICAL_NOT
976 t_op.ast_link = a_not
977 return ast.Unary_Expression(
978 mh = self.mh,
979 location = t_op.location,
980 typ = self.builtin_bool,
981 operator = a_not,
982 n_operand = n_operand)
984 elif self.peek_kw("abs"):
985 self.match_kw("abs")
986 t_op = self.ct
987 n_operand = self.parse_primary(scope)
988 a_abs = ast.Unary_Operator.ABSOLUTE_VALUE
989 t_op.ast_link = a_abs
990 return ast.Unary_Expression(
991 mh = self.mh,
992 location = t_op.location,
993 typ = n_operand.typ,
994 operator = a_abs,
995 n_operand = n_operand)
997 else:
998 n_lhs = self.parse_primary(scope)
999 if self.peek("OPERATOR") and self.nt.value == "**":
1000 self.match("OPERATOR")
1001 t_op = self.ct
1002 n_rhs = self.parse_primary(scope)
1003 rhs_value = n_rhs.evaluate(self.mh, None)
1004 a_binary = ast.Binary_Operator.POWER
1005 t_op.ast_link = a_binary
1006 n_lhs = ast.Binary_Expression(
1007 mh = self.mh,
1008 location = t_op.location,
1009 typ = n_lhs.typ,
1010 operator = a_binary,
1011 n_lhs = n_lhs,
1012 n_rhs = n_rhs)
1013 if rhs_value.value < 0: 1013 ↛ 1014line 1013 didn't jump to line 1014 because the condition on line 1013 was never true
1014 self.mh.error(n_rhs.location,
1015 "exponent must not be negative")
1016 return n_lhs
1018 def parse_primary(self, scope):
1019 # lobster-trace: LRM.Primary
1020 assert isinstance(scope, ast.Scope)
1022 if self.peek("INTEGER"):
1023 # lobster-trace: LRM.Integer_Values
1024 self.match("INTEGER")
1025 int_lit = ast.Integer_Literal(self.ct, self.builtin_int)
1026 int_lit.set_ast_link(self.ct)
1027 return int_lit
1029 elif self.peek("DECIMAL"):
1030 # lobster-trace: LRM.Decimal_Values
1031 self.match("DECIMAL")
1032 dec_lit = ast.Decimal_Literal(self.ct, self.builtin_decimal)
1033 dec_lit.set_ast_link(self.ct)
1034 return dec_lit
1036 elif self.peek("STRING"):
1037 # lobster-trace: LRM.String_Values
1038 self.match("STRING")
1039 string_lit = ast.String_Literal(self.ct, self.builtin_str)
1040 string_lit.set_ast_link(self.ct)
1041 return string_lit
1043 elif self.peek_kw("true") or self.peek_kw("false"):
1044 # lobster-trace: LRM.Boolean_Values
1045 self.match("KEYWORD")
1046 bool_lit = ast.Boolean_Literal(self.ct, self.builtin_bool)
1047 bool_lit.set_ast_link(self.ct)
1048 return bool_lit
1050 elif self.peek_kw("null"):
1051 self.match_kw("null")
1052 null_lit = ast.Null_Literal(self.ct)
1053 null_lit.set_ast_link(self.ct)
1054 return null_lit
1056 elif self.peek("BRA"):
1057 self.match("BRA")
1058 t_bra = self.ct
1059 if self.peek_kw("forall") or self.peek_kw("exists"):
1060 rv = self.parse_quantified_expression(scope)
1061 elif self.peek_kw("if"):
1062 rv = self.parse_conditional_expression(scope)
1063 else:
1064 rv = self.parse_expression(scope)
1065 rv.set_ast_link(t_bra)
1066 self.match("KET")
1067 rv.set_ast_link(self.ct)
1068 return rv
1070 else:
1071 return self.parse_name(scope)
1073 def parse_quantified_expression(self, scope):
1074 # lobster-trace: LRM.Quantified_Expression
1075 assert isinstance(scope, ast.Scope)
1077 if self.peek_kw("forall"):
1078 self.match_kw("forall")
1079 t_quantified = self.ct
1080 universal = True
1081 else:
1082 self.match_kw("exists")
1083 t_quantified = self.ct
1084 universal = False
1085 loc = self.ct.location
1086 self.match("IDENTIFIER")
1087 t_qv = self.ct
1088 if scope.contains(t_qv.value):
1089 # lobster-trace: LRM.Quantification_Naming_Scope
1090 pdef = scope.lookup(self.mh, t_qv)
1091 self.mh.error(t_qv.location,
1092 "shadows %s %s from %s" %
1093 (pdef.__class__.__name__,
1094 pdef.name,
1095 self.mh.cross_file_reference(pdef.location)))
1096 self.match_kw("in")
1097 t_in = self.ct
1098 self.match("IDENTIFIER")
1099 field = scope.lookup(self.mh, self.ct, ast.Composite_Component)
1100 n_source = ast.Name_Reference(self.ct.location,
1101 field)
1102 n_source.set_ast_link(self.ct)
1103 if not isinstance(field.n_typ, ast.Array_Type):
1104 # lobster-trace: LRM.Quantification_Object
1105 self.mh.error(self.ct.location,
1106 "you can only quantify over arrays")
1107 n_var = ast.Quantified_Variable(t_qv.value,
1108 t_qv.location,
1109 field.n_typ.element_type)
1110 n_var.set_ast_link(t_qv)
1111 self.match("ARROW")
1112 t_arrow = self.ct
1114 new_table = ast.Symbol_Table()
1115 new_table.register(self.mh, n_var)
1116 scope.push(new_table)
1117 n_expr = self.parse_expression(scope)
1118 scope.pop()
1120 quantified_expression = ast.Quantified_Expression(
1121 mh = self.mh,
1122 location = loc,
1123 typ = self.builtin_bool,
1124 universal = universal,
1125 n_variable = n_var,
1126 n_source = n_source,
1127 n_expr = n_expr)
1129 quantified_expression.set_ast_link(t_quantified)
1130 quantified_expression.set_ast_link(t_in)
1131 quantified_expression.set_ast_link(t_arrow)
1133 return quantified_expression
1135 def parse_conditional_expression(self, scope):
1136 # lobster-trace: LRM.Conditional_Expression
1137 # lobster-trace: LRM.Restricted_Null
1138 assert isinstance(scope, ast.Scope)
1140 self.match_kw("if")
1141 t_if = self.ct
1142 if_cond = self.parse_expression(scope)
1143 self.match_kw("then")
1144 t_then = self.ct
1145 if_expr = self.parse_expression(scope)
1146 if if_expr.typ is None:
1147 self.mh.error(if_expr.location,
1148 "null is not permitted here")
1149 if_action = ast.Action(self.mh, t_if, if_cond, if_expr)
1151 rv = ast.Conditional_Expression(location = t_if.location,
1152 if_action = if_action)
1153 if_action.set_ast_link(t_if)
1154 if_action.set_ast_link(t_then)
1156 while self.peek_kw("elsif"):
1157 self.match_kw("elsif")
1158 t_elsif = self.ct
1159 elsif_cond = self.parse_expression(scope)
1160 self.match_kw("then")
1161 t_then = self.ct
1162 elsif_expr = self.parse_expression(scope)
1163 elsif_action = ast.Action(self.mh, t_elsif, elsif_cond, elsif_expr)
1164 elsif_action.set_ast_link(t_elsif)
1165 elsif_action.set_ast_link(t_then)
1166 rv.add_elsif(self.mh, elsif_action)
1168 self.match_kw("else")
1169 rv.set_ast_link(self.ct)
1170 else_expr = self.parse_expression(scope)
1171 rv.set_else_part(self.mh, else_expr)
1173 return rv
1175 def parse_builtin(self, scope, n_name, t_name):
1176 # lobster-trace: LRM.Builtin_Functions
1177 # lobster-trace: LRM.Builtin_Type_Conversion_Functions
1178 assert isinstance(scope, ast.Scope)
1179 assert isinstance(n_name, (ast.Builtin_Function,
1180 ast.Builtin_Numeric_Type))
1181 assert isinstance(t_name, Token)
1183 # Parse the arguments.
1184 parameters = []
1185 n_name.set_ast_link(self.ct)
1186 self.match("BRA")
1187 n_name.set_ast_link(self.ct)
1188 while not self.peek("KET"): 1188 ↛ 1199line 1188 didn't jump to line 1199 because the condition on line 1188 was always true
1189 exp = self.parse_expression(scope)
1190 if not self.ct.ast_link: 1190 ↛ 1191line 1190 didn't jump to line 1191 because the condition on line 1190 was never true
1191 exp.set_ast_link(self.ct)
1192 parameters.append(exp)
1194 if self.peek("COMMA"): 1194 ↛ 1198line 1194 didn't jump to line 1198 because the condition on line 1194 was always true
1195 self.match("COMMA")
1196 n_name.set_ast_link(self.ct)
1197 else:
1198 break
1199 self.match("KET")
1200 n_name.set_ast_link(self.ct)
1202 # Enforce arity
1203 if isinstance(n_name, ast.Builtin_Function):
1204 required_arity = n_name.arity
1205 precise = not n_name.arity_at_least
1206 else:
1207 required_arity = 1
1208 precise = True
1210 if precise:
1211 if required_arity != len(parameters):
1212 self.mh.error(t_name.location,
1213 "function requires %u parameters" %
1214 n_name.arity)
1215 else:
1216 if required_arity > len(parameters): 1216 ↛ 1217line 1216 didn't jump to line 1217 because the condition on line 1216 was never true
1217 self.mh.error(t_name.location,
1218 "function requires at least %u parameters" %
1219 n_name.arity)
1221 # Enforce types
1222 if n_name.name == "len":
1223 if isinstance(parameters[0].typ, ast.Builtin_String):
1224 return ast.Unary_Expression(
1225 mh = self.mh,
1226 location = t_name.location,
1227 typ = self.builtin_int,
1228 operator = ast.Unary_Operator.STRING_LENGTH,
1229 n_operand = parameters[0])
1230 else:
1231 return ast.Unary_Expression(
1232 mh = self.mh,
1233 location = t_name.location,
1234 typ = self.builtin_int,
1235 operator = ast.Unary_Operator.ARRAY_LENGTH,
1236 n_operand = parameters[0])
1238 elif n_name.name in ("startswith",
1239 "endswith"):
1240 return ast.Binary_Expression(
1241 mh = self.mh,
1242 location = t_name.location,
1243 typ = self.builtin_bool,
1244 operator = (ast.Binary_Operator.STRING_STARTSWITH
1245 if "startswith" in n_name.name
1246 else ast.Binary_Operator.STRING_ENDSWITH),
1247 n_lhs = parameters[0],
1248 n_rhs = parameters[1])
1250 elif n_name.name == "matches":
1251 parameters[1].ensure_type(self.mh, ast.Builtin_String)
1252 try:
1253 # lobster-trace: LRM.Static_Regular_Expression
1254 # scope is None on purpose to enforce static context
1255 value = parameters[1].evaluate(self.mh, None)
1256 assert isinstance(value.typ, ast.Builtin_String)
1257 re.compile(value.value)
1258 except re.error as err:
1259 self.mh.error(value.location,
1260 str(err))
1261 return ast.Binary_Expression(
1262 mh = self.mh,
1263 location = t_name.location,
1264 typ = self.builtin_bool,
1265 operator = ast.Binary_Operator.STRING_REGEX,
1266 n_lhs = parameters[0],
1267 n_rhs = parameters[1])
1269 elif n_name.name == "oneof":
1270 return ast.OneOf_Expression(
1271 mh = self.mh,
1272 location = t_name.location,
1273 typ = self.builtin_bool,
1274 choices = parameters)
1276 elif isinstance(n_name, ast.Builtin_Numeric_Type):
1277 parameters[0].ensure_type(self.mh, ast.Builtin_Numeric_Type)
1278 if isinstance(n_name, ast.Builtin_Integer):
1279 return ast.Unary_Expression(
1280 mh = self.mh,
1281 location = t_name.location,
1282 typ = self.builtin_int,
1283 operator = ast.Unary_Operator.CONVERSION_TO_INT,
1284 n_operand = parameters[0])
1285 elif isinstance(n_name, ast.Builtin_Decimal):
1286 return ast.Unary_Expression(
1287 mh = self.mh,
1288 location = t_name.location,
1289 typ = self.builtin_decimal,
1290 operator = ast.Unary_Operator.CONVERSION_TO_DECIMAL,
1291 n_operand = parameters[0])
1292 else:
1293 self.mh.ice_loc(t_name.location,
1294 "unexpected type conversion")
1296 else:
1297 self.mh.ice_loc(t_name.location,
1298 "unexpected builtin")
1300 def parse_name(self, scope):
1301 # lobster-trace: LRM.Names
1303 # This is a bit more complex. The grammar is:
1304 #
1305 # qualified_name ::= [ IDENTIFIER_package_name '.' ] IDENTIFIER_name
1306 #
1307 # name ::= qualified_name
1308 # | name '.' IDENTIFIER
1309 # | name '[' expression ']'
1310 # | name '(' parameter_list ')'
1311 #
1312 # parameter_list ::= expression { ',' expression }
1314 assert isinstance(scope, ast.Scope)
1316 # All names start with a (qualified) identifier. We parse that
1317 # first. There is a special complication for functions, as
1318 # builtin functions (e.g. len) can shadow record
1319 # components. However as functions cannot be stored in
1320 # components the true grammar for function calls is always
1321 # IDENTIFIER '('; so we can slightly special case this.
1323 # lobster-trace: LRM.Builtin_Functions
1324 # lobster-trace: LRM.Builtin_Type_Conversion_Functions
1325 self.match("IDENTIFIER")
1326 if self.peek("BRA"):
1327 # If we follow our name with brackets
1328 # immediately, we have a builtin function call.
1329 n_name = self.stab.lookup(self.mh,
1330 self.ct)
1331 if not isinstance(n_name, (ast.Builtin_Function, 1331 ↛ 1333line 1331 didn't jump to line 1333 because the condition on line 1331 was never true
1332 ast.Builtin_Numeric_Type)):
1333 self.mh.error(self.ct.location,
1334 "not a valid builtin function "
1335 "or numeric type")
1336 else:
1337 n_name = self.parse_qualified_name(scope, match_ident=False)
1339 # Enum literals are a bit different, so we deal with them
1340 # first.
1341 if isinstance(n_name, ast.Enumeration_Type):
1342 n_name.set_ast_link(self.ct)
1343 self.match("DOT")
1344 n_name.set_ast_link(self.ct)
1345 self.match("IDENTIFIER")
1346 lit = n_name.literals.lookup(self.mh,
1347 self.ct,
1348 ast.Enumeration_Literal_Spec)
1349 enum_lit = ast.Enumeration_Literal(location = self.ct.location,
1350 literal = lit)
1351 enum_lit.set_ast_link(self.ct)
1352 return enum_lit
1354 # Anything that remains is either a function call or an actual
1355 # name. Let's just enforce this for sanity.
1356 if not isinstance(n_name, (ast.Builtin_Function, 1356 ↛ 1361line 1356 didn't jump to line 1361 because the condition on line 1356 was never true
1357 ast.Builtin_Numeric_Type,
1358 ast.Composite_Component,
1359 ast.Quantified_Variable,
1360 )):
1361 self.mh.error(self.ct.location,
1362 "%s %s is not a valid name" %
1363 (n_name.__class__.__name__,
1364 n_name.name))
1366 # Right now function calls and type conversions must be
1367 # top-level, so let's get these out of the way as well.
1368 if isinstance(n_name, (ast.Builtin_Function,
1369 ast.Builtin_Numeric_Type)):
1370 # lobster-trace: LRM.Builtin_Functions
1371 # lobster-trace: LRM.Builtin_Type_Conversion_Functions
1372 return self.parse_builtin(scope, n_name, self.ct)
1374 assert isinstance(n_name, (ast.Composite_Component,
1375 ast.Quantified_Variable))
1377 # We now process the potentially recursive part:
1378 # | name '.' IDENTIFIER
1379 # | name '[' expression ']'
1380 n_name = ast.Name_Reference(location = self.ct.location,
1381 entity = n_name)
1382 n_name.set_ast_link(self.ct)
1383 while self.peek("DOT") or self.peek("S_BRA"):
1384 if self.peek("DOT"):
1385 if not isinstance(n_name.typ, ast.Tuple_Type):
1386 # lobster-trace: LRM.Valid_Index_Prefixes
1387 self.mh.error(n_name.location,
1388 "expression '%s' has type %s, "
1389 "which is not a tuple" %
1390 (n_name.to_string(),
1391 n_name.typ.name))
1392 self.match("DOT")
1393 t_dot = self.ct
1394 self.match("IDENTIFIER")
1395 n_field = n_name.typ.components.lookup(self.mh,
1396 self.ct,
1397 ast.Composite_Component)
1398 n_field.set_ast_link(self.ct)
1399 n_name = ast.Field_Access_Expression(
1400 mh = self.mh,
1401 location = self.ct.location,
1402 n_prefix = n_name,
1403 n_field = n_field)
1404 n_name.set_ast_link(t_dot)
1406 elif self.peek("S_BRA"): 1406 ↛ 1383line 1406 didn't jump to line 1383 because the condition on line 1406 was always true
1407 if not isinstance(n_name.typ, ast.Array_Type):
1408 self.mh.error(n_name.location,
1409 "expression '%s' has type %s, "
1410 "which is not an array" %
1411 (n_name.to_string(),
1412 n_name.typ.name))
1414 self.match("S_BRA")
1415 t_bracket = self.ct
1416 n_index = self.parse_expression(scope)
1417 self.match("S_KET")
1418 a_binary = ast.Binary_Operator.INDEX
1419 t_bracket.ast_link = a_binary
1420 self.ct.ast_link = a_binary
1422 n_name = ast.Binary_Expression(
1423 mh = self.mh,
1424 location = t_bracket.location,
1425 typ = n_name.typ.element_type,
1426 operator = a_binary,
1427 n_lhs = n_name,
1428 n_rhs = n_index)
1430 return n_name
1432 def parse_check_block(self):
1433 # lobster-trace: LRM.Check_Block
1434 t_severity = None
1435 self.match_kw("checks")
1436 t_checks = self.ct
1437 self.match("IDENTIFIER")
1438 # lobster-trace: LRM.Applicable_Types
1439 # lobster-trace: LRM.Applicable_Components
1440 n_ctype = self.cu.package.symbols.lookup(self.mh,
1441 self.ct,
1442 ast.Composite_Type)
1443 n_check_block = ast.Check_Block(location = self.ct.location,
1444 n_typ = n_ctype)
1445 n_check_block.set_ast_link(t_checks)
1446 n_ctype.set_ast_link(self.ct)
1447 scope = ast.Scope()
1448 scope.push(self.stab)
1449 scope.push(self.cu.package.symbols)
1450 scope.push(n_ctype.components)
1451 self.match("C_BRA")
1452 n_check_block.set_ast_link(self.ct)
1453 while not self.peek("C_KET"):
1454 c_expr = self.parse_expression(scope)
1455 if not isinstance(c_expr.typ, ast.Builtin_Boolean): 1455 ↛ 1456line 1455 didn't jump to line 1456 because the condition on line 1455 was never true
1456 self.mh.error(c_expr.location,
1457 "check expression must be Boolean")
1459 self.match("COMMA")
1460 t_first_comma = self.ct
1461 if self.peek("KEYWORD"):
1462 self.match("KEYWORD")
1463 t_severity = self.ct
1464 if self.ct.value not in ("warning", "error", "fatal"): 1464 ↛ 1465line 1464 didn't jump to line 1465 because the condition on line 1464 was never true
1465 self.mh.error(self.ct.location,
1466 "expected warning|error|fatal")
1467 c_sev = self.ct.value
1468 else:
1469 c_sev = "error"
1471 self.match("STRING")
1472 if "\n" in self.ct.value:
1473 # lobster-trace: LRM.No_Newlines_In_Message
1474 self.mh.error(self.ct.location,
1475 "error message must not contain a newline",
1476 fatal = False)
1477 t_msg = self.ct
1479 has_extrainfo = False
1480 has_anchor = False
1481 if self.peek("COMMA"):
1482 self.match("COMMA")
1483 t_second_comma = self.ct
1484 if self.peek("IDENTIFIER"):
1485 has_anchor = True
1486 elif self.peek("STRING"): 1486 ↛ 1489line 1486 didn't jump to line 1489 because the condition on line 1486 was always true
1487 has_extrainfo = True
1488 else:
1489 self.mh.error(self.nt.location,
1490 "expected either a details string or"
1491 " identifier to anchor the check message")
1493 if has_extrainfo:
1494 self.match("STRING")
1495 t_extrainfo = self.ct
1496 c_extrainfo = self.ct.value
1498 if self.peek("COMMA"): 1498 ↛ 1499line 1498 didn't jump to line 1499 because the condition on line 1498 was never true
1499 self.match("COMMA")
1500 t_third_comma = self.ct
1501 has_anchor = True
1503 else:
1504 c_extrainfo = None
1506 if has_anchor:
1507 self.match("IDENTIFIER")
1508 t_anchor = self.ct
1509 c_anchor = n_ctype.components.lookup(self.mh,
1510 self.ct,
1511 ast.Composite_Component)
1512 else:
1513 c_anchor = None
1515 n_check = ast.Check(n_type = n_ctype,
1516 n_expr = c_expr,
1517 n_anchor = c_anchor,
1518 severity = c_sev,
1519 t_message = t_msg,
1520 extrainfo = c_extrainfo)
1522 # pylint: disable=possibly-used-before-assignment
1523 # pylint: disable=used-before-assignment
1525 n_check.set_ast_link(t_first_comma)
1526 if t_severity:
1527 n_check.set_ast_link(t_severity)
1528 n_check.set_ast_link(t_msg)
1529 if c_extrainfo or c_anchor:
1530 n_check.set_ast_link(t_second_comma)
1531 if c_extrainfo:
1532 n_check.set_ast_link(t_extrainfo)
1533 if c_anchor:
1534 c_anchor.set_ast_link(t_anchor)
1535 if c_anchor and c_extrainfo: 1535 ↛ 1536line 1535 didn't jump to line 1536 because the condition on line 1535 was never true
1536 n_check.set_ast_link(t_third_comma)
1538 n_ctype.add_check(n_check)
1539 n_check_block.add_check(n_check)
1541 assert scope.size() == 3
1543 self.match("C_KET")
1544 n_check_block.set_ast_link(self.ct)
1546 return n_check_block
1548 def parse_section_declaration(self):
1549 # lobster-trace: LRM.Section_Declaration
1550 self.match_kw("section")
1551 t_section = self.ct
1552 self.match("STRING")
1553 sec = ast.Section(name = self.ct.value,
1554 location = self.ct.location,
1555 parent = self.section[-1] if self.section else None)
1556 sec.set_ast_link(self.ct)
1557 sec.set_ast_link(t_section)
1558 self.section.append(sec)
1559 self.match("C_BRA")
1560 sec.set_ast_link(self.ct)
1561 while not self.peek("C_KET"):
1562 self.parse_trlc_entry()
1563 self.match("C_KET")
1564 sec.set_ast_link(self.ct)
1565 self.section.pop()
1567 def parse_boolean(self):
1568 # lobster-trace: LRM.Boolean_Values
1569 self.match("KEYWORD")
1570 if self.ct.value in ("true", "false"): 1570 ↛ 1573line 1570 didn't jump to line 1573 because the condition on line 1570 was always true
1571 return ast.Boolean_Literal(self.ct, self.builtin_bool)
1572 else:
1573 self.mh.error(self.ct.location,
1574 "expected boolean literal (true or false)")
1576 def parse_value(self, typ):
1577 # lobster-trace: LRM.Tuple_Syntax_Correct_Form
1578 assert isinstance(typ, ast.Type)
1580 if isinstance(typ, ast.Builtin_Numeric_Type):
1581 # lobster-trace: LRM.Integer_Values
1582 # lobster-trace: LRM.Decimal_Values
1583 if self.peek("OPERATOR") and \
1584 self.nt.value in Parser.ADDING_OPERATOR:
1585 self.match("OPERATOR")
1586 t_op = self.ct
1587 e_op = (ast.Unary_Operator.PLUS
1588 if t_op.value == "+"
1589 else ast.Unary_Operator.MINUS)
1590 t_op.ast_link = e_op
1591 else:
1592 t_op = None
1594 if isinstance(typ, ast.Builtin_Decimal):
1595 self.match("DECIMAL")
1596 rv = ast.Decimal_Literal(self.ct, self.builtin_decimal)
1597 rv.set_ast_link(self.ct)
1598 elif isinstance(typ, ast.Builtin_Integer):
1599 self.match("INTEGER")
1600 rv = ast.Integer_Literal(self.ct, self.builtin_int)
1601 rv.set_ast_link(self.ct)
1602 else:
1603 assert False
1605 if t_op:
1606 rv = ast.Unary_Expression(mh = self.mh,
1607 location = t_op.location,
1608 typ = rv.typ,
1609 operator = e_op,
1610 n_operand = rv)
1612 return rv
1614 elif isinstance(typ, ast.Builtin_Markup_String):
1615 # lobster-trace: LRM.Markup_String_Values
1616 return self.parse_markup_string()
1618 elif isinstance(typ, ast.Builtin_String):
1619 # lobster-trace: LRM.String_Values
1620 self.match("STRING")
1621 rv = ast.String_Literal(self.ct, self.builtin_str)
1622 rv.set_ast_link(self.ct)
1623 return rv
1625 elif isinstance(typ, ast.Builtin_Boolean):
1626 rv = self.parse_boolean()
1627 rv.set_ast_link(self.ct)
1628 return rv
1630 elif isinstance(typ, ast.Array_Type):
1631 self.match("S_BRA")
1632 rv = ast.Array_Aggregate(self.ct.location,
1633 typ)
1634 rv.set_ast_link(self.ct)
1635 while not self.peek("S_KET"):
1636 array_elem = self.parse_value(typ.element_type)
1637 rv.append(array_elem)
1638 if self.peek("COMMA"):
1639 self.match("COMMA")
1640 rv.set_ast_link(self.ct)
1641 elif self.peek("S_KET") or self.nt is None: 1641 ↛ anywhereline 1641 didn't jump anywhere: it always raised an exception.
1642 break
1643 else:
1644 self.mh.error(self.ct.location,
1645 "comma separating array elements is "
1646 "missing",
1647 fatal = False)
1649 self.match("S_KET")
1650 rv.set_ast_link(self.ct)
1652 if len(rv.value) < typ.lower_bound:
1653 self.mh.error(self.ct.location,
1654 "this array requires at least %u elements "
1655 "(only %u provided)" %
1656 (typ.lower_bound,
1657 len(rv.value)),
1658 fatal=False)
1659 if typ.upper_bound and len(rv.value) > typ.upper_bound:
1660 self.mh.error(rv.value[typ.upper_bound].location,
1661 "this array requires at most %u elements "
1662 "(%u provided)" %
1663 (typ.upper_bound,
1664 len(rv.value)),
1665 fatal=False)
1667 return rv
1669 elif isinstance(typ, ast.Enumeration_Type):
1670 enum = self.parse_qualified_name(self.default_scope,
1671 ast.Enumeration_Type)
1672 enum.set_ast_link(self.ct)
1673 if enum != typ:
1674 self.mh.error(self.ct.location,
1675 "expected %s" % typ.name)
1676 self.match("DOT")
1677 enum.set_ast_link(self.ct)
1678 self.match("IDENTIFIER")
1679 lit = enum.literals.lookup(self.mh,
1680 self.ct,
1681 ast.Enumeration_Literal_Spec)
1682 return ast.Enumeration_Literal(self.ct.location,
1683 lit)
1685 elif isinstance(typ, ast.Record_Type):
1686 self.match("IDENTIFIER")
1687 t_name = self.ct
1688 if self.peek("DOT"):
1689 self.match("DOT")
1690 t_dot = self.ct
1691 self.match("IDENTIFIER")
1692 the_pkg = self.stab.lookup(self.mh, t_name, ast.Package)
1693 the_pkg.set_ast_link(t_name)
1694 the_pkg.set_ast_link(t_dot)
1695 if not self.cu.is_visible(the_pkg): 1695 ↛ 1696line 1695 didn't jump to line 1696 because the condition on line 1695 was never true
1696 self.mh.error(self.ct.location,
1697 "package must be imported before use")
1698 t_name = self.ct
1699 else:
1700 the_pkg = self.cu.package
1702 rv = ast.Record_Reference(location = t_name.location,
1703 name = t_name.value,
1704 typ = typ,
1705 package = the_pkg)
1706 rv.set_ast_link(t_name)
1708 # We can do an early lookup if the target is known
1709 if the_pkg.symbols.contains(t_name.value):
1710 rv.resolve_references(self.mh)
1712 return rv
1714 elif isinstance(typ, ast.Tuple_Type) and typ.has_separators():
1715 # lobster-trace: LRM.Tuple_Separator_Form
1716 rv = ast.Tuple_Aggregate(self.nt.location, typ)
1718 next_is_optional = False
1719 for n_item in typ.iter_sequence():
1720 if isinstance(n_item, ast.Composite_Component):
1721 if next_is_optional and n_item.optional:
1722 break
1723 value = self.parse_value(n_item.n_typ)
1724 rv.assign(n_item.name, value)
1726 elif n_item.token.kind in ("AT", "COLON", "SEMICOLON"):
1727 if self.peek(n_item.token.kind):
1728 self.match(n_item.token.kind)
1729 n_item.set_ast_link(self.ct)
1730 else:
1731 next_is_optional = True
1733 elif n_item.token.kind == "IDENTIFIER":
1734 if self.peek("IDENTIFIER") and \
1735 self.nt.value == n_item.token.value:
1736 self.match("IDENTIFIER")
1737 n_item.set_ast_link(self.ct)
1738 else:
1739 next_is_optional = True
1741 else:
1742 assert False
1744 return rv
1746 elif isinstance(typ, ast.Tuple_Type) and not typ.has_separators():
1747 # lobster-trace: LRM.Tuple_Generic_Form
1748 self.match("BRA")
1749 rv = ast.Tuple_Aggregate(self.ct.location, typ)
1750 rv.set_ast_link(self.ct)
1752 first = True
1753 for n_field in typ.iter_sequence():
1754 if first:
1755 first = False
1756 else:
1757 self.match("COMMA")
1758 rv.set_ast_link(self.ct)
1759 rv.assign(n_field.name,
1760 self.parse_value(n_field.n_typ))
1762 self.match("KET")
1763 rv.set_ast_link(self.ct)
1764 return rv
1766 else:
1767 self.mh.ice_loc(self.ct.location,
1768 "logic error: unexpected type %s" %
1769 typ.__class__.__name__)
1771 def parse_markup_string(self):
1772 # lobster-trace: LRM.Markup_String_Values
1773 self.match("STRING")
1774 rv = ast.String_Literal(self.ct, self.builtin_mstr)
1775 mpar = Markup_Parser(self, rv)
1776 mpar.parse_all_references()
1777 return rv
1779 def parse_record_object_declaration(self):
1780 # lobster-trace: LRM.Section_Declaration
1781 # lobster-trace: LRM.Record_Object_Declaration
1782 # lobster-trace: LRM.Valid_Record_Types
1783 # lobster-trace: LRM.Valid_Components
1784 # lobster-trace: LRM.Valid_Enumeration_Literals
1785 # lobster-trace: LRM.Mandatory_Components
1786 # lobster-trace: LRM.Evaluation_Of_Checks
1787 # lobster-trace: LRM.Single_Value_Assignment
1789 r_typ = self.parse_qualified_name(self.default_scope,
1790 ast.Record_Type)
1791 r_typ.set_ast_link(self.ct)
1792 if r_typ.is_abstract:
1793 self.mh.error(self.ct.location,
1794 "cannot declare object of abstract record type %s" %
1795 r_typ.name)
1797 self.match("IDENTIFIER")
1798 obj = ast.Record_Object(
1799 name = self.ct.value,
1800 location = self.ct.location,
1801 n_typ = r_typ,
1802 section = self.section.copy() if self.section else None,
1803 n_package = self.cu.package)
1804 self.cu.package.symbols.register(self.mh, obj)
1805 obj.set_ast_link(self.ct)
1807 self.match("C_BRA")
1808 obj.set_ast_link(self.ct)
1809 while not self.peek("C_KET"):
1810 self.match("IDENTIFIER")
1811 comp = r_typ.components.lookup(self.mh,
1812 self.ct,
1813 ast.Composite_Component)
1814 if obj.is_component_implicit_null(comp):
1815 self.mh.error(self.ct.location,
1816 "component '%s' already assigned at line %i" %
1817 (comp.name,
1818 obj.field[comp.name].location.line_no))
1819 comp.set_ast_link(self.ct)
1820 if r_typ.is_frozen(comp):
1821 self.mh.error(self.ct.location,
1822 "cannot overwrite frozen component %s" %
1823 comp.name)
1824 self.match("ASSIGN")
1825 comp.set_ast_link(self.ct)
1826 value = self.parse_value(comp.n_typ)
1827 if not self.ct.ast_link:
1828 value.set_ast_link(self.ct)
1829 obj.assign(comp, value)
1831 # Check that each non-optional component has been specified
1832 for comp in r_typ.all_components():
1833 if isinstance(obj.field[comp.name], ast.Implicit_Null):
1834 if r_typ.is_frozen(comp):
1835 obj.assign(comp, r_typ.get_freezing_expression(comp))
1836 elif not comp.optional:
1837 self.mh.error(
1838 obj.location,
1839 "required component %s (see %s) is not defined" %
1840 (comp.name,
1841 self.mh.cross_file_reference(comp.location)))
1843 self.match("C_KET")
1844 obj.set_ast_link(self.ct)
1846 return obj
1848 def parse_trlc_entry(self):
1849 # lobster-trace: LRM.TRLC_File
1850 if self.peek_kw("section"):
1851 self.parse_section_declaration()
1852 else:
1853 self.cu.add_item(self.parse_record_object_declaration())
1855 def parse_preamble(self, kind):
1856 assert kind in ("rsl", "trlc")
1857 # lobster-trace: LRM.Layout
1858 # lobster-trace: LRM.Preamble
1860 # First, parse package indication, declaring the package if
1861 # needed
1862 self.match_kw("package")
1863 t_pkg = self.ct
1864 self.match("IDENTIFIER")
1866 if kind == "rsl":
1867 declare_package = True
1868 else:
1869 # lobster-trace: LRM.Late_Package_Declarations
1870 declare_package = not self.stab.contains(self.ct.value)
1872 if declare_package:
1873 # lobster-trace: LRM.Package_Declaration
1874 pkg = ast.Package(name = self.ct.value,
1875 location = self.ct.location,
1876 builtin_stab = self.stab,
1877 declared_late = kind == "trlc")
1878 self.stab.register(self.mh, pkg)
1879 else:
1880 pkg = self.stab.lookup(self.mh, self.ct, ast.Package)
1882 pkg.set_ast_link(t_pkg)
1883 pkg.set_ast_link(self.ct)
1885 # lobster-trace: LRM.Current_Package
1886 self.cu.set_package(pkg)
1888 self.default_scope.push(self.cu.package.symbols)
1890 # Second, parse import list (but don't resolve names yet)
1891 # lobster-trace: LRM.Import_Visibility
1892 if kind != "check": 1892 ↛ exitline 1892 didn't return from function 'parse_preamble' because the condition on line 1892 was always true
1893 while self.peek_kw("import"):
1894 self.match_kw("import")
1895 pkg.set_ast_link(self.ct)
1896 self.match("IDENTIFIER")
1897 self.cu.add_import(self.mh, self.ct)
1899 def parse_rsl_file(self):
1900 # lobster-trace: LRM.RSL_File
1901 assert self.cu.package is not None
1903 ok = True
1904 while not self.peek_eof():
1905 try:
1906 if self.peek_kw("checks"):
1907 self.cu.add_item(self.parse_check_block())
1908 else:
1909 self.cu.add_item(self.parse_type_declaration())
1910 except TRLC_Error as err:
1911 if not self.error_recovery or err.kind == "lex error": 1911 ↛ 1912line 1911 didn't jump to line 1912 because the condition on line 1911 was never true
1912 raise
1914 ok = False
1916 # Recovery strategy is to scan until we get the next
1917 # relevant keyword
1918 self.skip_until_newline()
1919 while not self.peek_eof():
1920 if self.peek_kw("checks") or \
1921 self.peek_kw("type") or \
1922 self.peek_kw("abstract") or \
1923 self.peek_kw("final") or \
1924 self.peek_kw("tuple") or \
1925 self.peek_kw("enum"):
1926 break
1927 self.advance()
1928 self.skip_until_newline()
1930 self.match_eof()
1932 for tok in self.lexer.tokens:
1933 if tok.kind == "COMMENT":
1934 self.cu.package.set_ast_link(tok)
1936 return ok
1938 def parse_trlc_file(self):
1939 # lobster-trace: LRM.TRLC_File
1940 assert self.cu.package is not None
1942 ok = True
1944 while self.peek_kw("section") or self.peek("IDENTIFIER"):
1945 try:
1946 self.parse_trlc_entry()
1947 except TRLC_Error as err:
1948 if not self.error_recovery or err.kind == "lex error": 1948 ↛ 1949line 1948 didn't jump to line 1949 because the condition on line 1948 was never true
1949 raise
1951 ok = False
1953 # Recovery strategy is to keep going until we find an
1954 # identifier that is a package or type, or section, or
1955 # EOF
1956 self.skip_until_newline()
1957 while not self.peek_eof():
1958 if self.peek_kw("section"): 1958 ↛ 1959line 1958 didn't jump to line 1959 because the condition on line 1958 was never true
1959 break
1960 elif not self.peek("IDENTIFIER"):
1961 pass
1962 elif self.stab.contains(self.nt.value): 1962 ↛ 1963line 1962 didn't jump to line 1963 because the condition on line 1962 was never true
1963 n_sym = self.stab.lookup_assuming(self.mh,
1964 self.nt.value)
1965 if isinstance(n_sym, ast.Package):
1966 break
1967 elif self.cu.package.symbols.contains(self.nt.value):
1968 n_sym = self.cu.package.symbols.lookup_assuming(
1969 self.mh,
1970 self.nt.value)
1971 if isinstance(n_sym, ast.Record_Type):
1972 break
1973 self.advance()
1974 self.skip_until_newline()
1976 self.match_eof()
1978 for tok in self.lexer.tokens:
1979 if tok.kind == "COMMENT":
1980 self.cu.package.set_ast_link(tok)
1982 return ok