Coverage for src/m6rclib/metaphor_formatters.py: 100%

30 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2025-01-22 17:09 +0000

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

2 

3import io 

4from typing import List, TextIO, Dict, Final 

5 

6from .metaphor_ast_node import MetaphorASTNode, MetaphorASTNodeType 

7from .metaphor_parser import MetaphorParserSyntaxError 

8 

9 

10NODE_TYPE_MAP: Final[Dict[MetaphorASTNodeType, str]] = { 

11 MetaphorASTNodeType.ACTION: "Action:", 

12 MetaphorASTNodeType.CONTEXT: "Context:", 

13 MetaphorASTNodeType.ROLE: "Role:" 

14} 

15 

16 

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

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

19 

20 Args: 

21 node: The root node to format 

22 

23 Returns: 

24 Formatted string representation of the AST 

25 """ 

26 output = io.StringIO() 

27 _format_node(node, 0, output) 

28 return output.getvalue() 

29 

30 

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

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

33 

34 Args: 

35 node: Current node being processed 

36 depth: Current tree depth 

37 out: Output buffer to write to 

38 """ 

39 if node.node_type != MetaphorASTNodeType.ROOT: 

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

41 if node.node_type == MetaphorASTNodeType.TEXT: 

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

43 return 

44 

45 keyword = NODE_TYPE_MAP.get(node.node_type, "") 

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

47 if node.value: 

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

49 out.write("\n") 

50 

51 for child in node.children: 

52 _format_node(child, depth + 1, out) 

53 

54 

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

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

57 

58 Args: 

59 errors: List of syntax errors to format 

60 

61 Returns: 

62 Formatted error string with each error on separate lines 

63 """ 

64 output = io.StringIO() 

65 

66 for error in errors: 

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

68 error_message = ( 

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

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

71 ) 

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

73 

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

75 return output.getvalue()