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
« 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.
15"""Functions for formatting Metaphor AST nodes and error messages."""
17import io
18from typing import List, TextIO
20from .metaphor_ast_node import MetaphorASTNode, MetaphorASTNodeType
21from .metaphor_parser import MetaphorParserSyntaxError
24def format_ast(node: MetaphorASTNode) -> str:
25 """Format an AST node and its children as a string.
27 Args:
28 node: The root node to format
30 Returns:
31 Formatted string representation of the AST
32 """
33 output = io.StringIO()
34 _format_node(node, 0, output)
35 return output.getvalue()
38def _format_node(node: MetaphorASTNode, depth: int, out: TextIO) -> None:
39 """Recursively format a node and its children.
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
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, "")
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")
66 for child in node.children:
67 _format_node(child, depth + 1, out)
70def format_errors(errors: List[MetaphorParserSyntaxError]) -> str:
71 """Format a list of syntax errors as a string.
73 Args:
74 errors: List of syntax errors to format
76 Returns:
77 Formatted error string with each error on separate lines
78 """
79 output = io.StringIO()
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")
89 output.write("----------------\n")
90 return output.getvalue()