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

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

2

3 import io

4 from typing import List, TextIO, Dict, Final

5

6 from .metaphor_ast_node import MetaphorASTNode, MetaphorASTNodeType

7 from .metaphor_parser import MetaphorParserSyntaxError

8

9

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

11 MetaphorASTNodeType.ACTION: "Action:",

12 MetaphorASTNodeType.CONTEXT: "Context:",

13 MetaphorASTNodeType.ROLE: "Role:"

14}

15

16

17 def 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

31 def _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

55 def 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()