Coverage for src/m6rclib/metaphor_formatter.py: 95%

31 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2024-11-14 16:54 +0000

1# Copyright 2024 M6R Ltd. 

2# 

3# Licensed under the Apache License, Version 2.0 (the "License"); 

4# you may not use this file except in compliance with the License. 

5# You may obtain a copy of the License at 

6# 

7# http://www.apache.org/licenses/LICENSE-2.0 

8# 

9# Unless required by applicable law or agreed to in writing, software 

10# distributed under the License is distributed on an "AS IS" BASIS, 

11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 

12# See the License for the specific language governing permissions and 

13# limitations under the License. 

14 

15"""Functions for formatting Metaphor AST nodes and error messages.""" 

16 

17import io 

18from typing import List, TextIO 

19 

20from .metaphor_ast_node import MetaphorASTNode, MetaphorASTNodeType 

21from .metaphor_parser import MetaphorParserSyntaxError 

22 

23 

24def format_ast(node: MetaphorASTNode) -> str: 

25 """Format an AST node and its children as a string. 

26  

27 Args: 

28 node: The root node to format 

29  

30 Returns: 

31 Formatted string representation of the AST 

32 """ 

33 output = io.StringIO() 

34 _format_node(node, 0, output) 

35 return output.getvalue() 

36 

37 

38def _format_node(node: MetaphorASTNode, depth: int, out: TextIO) -> None: 

39 """Recursively format a node and its children. 

40  

41 Args: 

42 node: Current node being processed 

43 depth: Current tree depth 

44 out: Output buffer to write to 

45 """ 

46 if node.node_type != MetaphorASTNodeType.ROOT: 

47 indent = " " * ((depth - 1) * 4) 

48 if node.node_type == MetaphorASTNodeType.TEXT: 

49 out.write(f"{indent}{node.value}\n") 

50 return 

51 

52 # Map node types to keywords 

53 node_type_map = { 

54 MetaphorASTNodeType.ACTION: "Action:", 

55 MetaphorASTNodeType.CONTEXT: "Context:", 

56 MetaphorASTNodeType.ROLE: "Role:" 

57 } 

58 keyword = node_type_map.get(node.node_type, "") 

59 

60 if keyword: 60 ↛ 66line 60 didn't jump to line 66 because the condition on line 60 was always true

61 out.write(f"{indent}{keyword}") 

62 if node.value: 62 ↛ 64line 62 didn't jump to line 64 because the condition on line 62 was always true

63 out.write(f" {node.value}") 

64 out.write("\n") 

65 

66 for child in node.children: 

67 _format_node(child, depth + 1, out) 

68 

69 

70def format_errors(errors: List[MetaphorParserSyntaxError]) -> str: 

71 """Format a list of syntax errors as a string. 

72  

73 Args: 

74 errors: List of syntax errors to format 

75  

76 Returns: 

77 Formatted error string with each error on separate lines 

78 """ 

79 output = io.StringIO() 

80 

81 for error in errors: 

82 caret = " " * (error.column - 1) 

83 error_message = ( 

84 f"{error.message}: line {error.line}, column {error.column}, " 

85 f"file {error.filename}\n{caret}|\n{caret}v\n{error.input_text}" 

86 ) 

87 output.write(f"----------------\n{error_message}\n") 

88 

89 output.write("----------------\n") 

90 return output.getvalue()