Coverage for lobster/tools/trlc/text_generation.py: 100%

34 statements  

« prev     ^ index     » next       coverage.py v7.10.7, created at 2026-09-22 04:43 +0000

1# LOBSTER - Lightweight Open BMW Software Traceability Evidence Report 

2# Copyright (C) 2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) 

3# 

4# This program is free software: you can redistribute it and/or modify 

5# it under the terms of the GNU Affero General Public License as 

6# published by the Free Software Foundation, either version 3 of the 

7# License, or (at your option) any later version. 

8# 

9# This program is distributed in the hope that it will be useful, but 

10# WITHOUT ANY WARRANTY; without even the implied warranty of 

11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 

12# Affero General Public License for more details. 

13# 

14# You should have received a copy of the GNU Affero General Public 

15# License along with this program. If not, see 

16# <https://www.gnu.org/licenses/>. 

17 

18from typing import Iterable, List 

19import re 

20from trlc import ast 

21from lobster.tools.trlc.errors import TupleComponentError 

22from lobster.tools.trlc.instruction import ( 

23 Instruction, InstructionType, ConstantInstruction, FieldInstruction, 

24) 

25 

26 

27PATTERN = re.compile(r"\$\([a-z][a-z0-9_]*\)", re.IGNORECASE) 

28 

29 

30def parse_instructions(text: str) -> List[Instruction]: 

31 """Parse a one-line text representing a list of instructions""" 

32 instructions = [] 

33 cpos = 0 

34 for match in PATTERN.finditer(text): 

35 if match.span()[0] > cpos: 

36 instructions.append(ConstantInstruction(text[cpos:match.span()[0]])) 

37 instructions.append(FieldInstruction(match.group(0)[2:-1])) 

38 cpos = match.span()[1] 

39 if cpos < len(text): 

40 instructions.append(ConstantInstruction(text[cpos:])) 

41 return instructions 

42 

43 

44def build_text_from_instructions( 

45 instructions: Iterable[Instruction], 

46 tuple_aggregate: ast.Tuple_Aggregate, 

47) -> str: 

48 tuple_data = tuple_aggregate.to_python_object() 

49 if not tuple_data: 

50 raise ValueError("Cannot convert empty TRLC tuple to text!") 

51 if not instructions: 

52 raise ValueError( 

53 f"'to_string' instructions for tuple '{tuple_aggregate.typ.name}' " 

54 f"are empty!", 

55 ) 

56 result = [] 

57 for instruction in instructions: 

58 if instruction.typ == InstructionType.CONSTANT_TEXT: 

59 result.append(instruction.value) 

60 elif instruction.typ == InstructionType.FIELD: 

61 component_value = tuple_data.get(instruction.value) 

62 # Note: If the field does not exist in the record type OR if the record 

63 # object is not fully populated, we raise an error. 

64 if component_value is None: 

65 raise TupleComponentError(instruction.value, tuple_aggregate) 

66 result.append(str(component_value)) 

67 else: 

68 raise ValueError(f"Unknown instruction type: {instruction.typ}") 

69 return "".join(result)