agent

Finished
2626
2727
2828def parse(input_string: str) -> SgfTree:
29 pass
29 """
30 Parse an SGF string and return a tree structure of properties.
31
32 Args:
33 input_string: A string in SGF format
34
35 Returns:
36 SgfTree: The parsed SGF tree
37
38 Raises:
39 ValueError: If the input is invalid according to SGF format rules
40 """
41 # Edge Case: Empty input
42 if not input_string:
43 raise ValueError("tree missing")
44
45 # Edge Case: Input doesn't start with '('
46 if not input_string.startswith('('):
47 raise ValueError("tree missing")
48
49 # Edge Case: Input doesn't end with ')'
50 if not input_string.endswith(')'):
51 raise ValueError("tree missing")
52
53 # Remove the outer parentheses
54 content = input_string[1:-1]
55
56 # Edge Case: Empty tree with no nodes
57 if not content:
58 raise ValueError("tree with no nodes")
59
60 return _parse_tree(content)
61
62
63def _parse_tree(content: str) -> SgfTree:
64 """
65 Parse the content inside the parentheses of an SGF tree.
66
67 Args:
68 content: The content inside the outer parentheses
69
70 Returns:
71 SgfTree: The parsed SGF tree
72 """
73 # Edge Case: Content starts with ';' - it's a node
74 if content.startswith(';'):
75 properties, remaining = _parse_properties(content[1:])
76 children = []
77
78 # Parse children if any
79 while remaining:
80 if remaining.startswith('('):
81 child, remaining = _parse_subtree(remaining)
82 children.append(child)
83 else:
84 # This is a shorthand for a single child
85 child_content = remaining
86 child = _parse_tree(';' + child_content)
87 children.append(child)
88 remaining = ''
89
90 return SgfTree(properties=properties, children=children)
91
92 # Edge Case: Content starts with '(' - it's a variation
93 elif content.startswith('('):
94 # This should not happen at the top level
95 raise ValueError("tree missing")
96
97 # Edge Case: Invalid content
98 else:
99 raise ValueError("tree missing")
100
101
102def _parse_subtree(content: str) -> tuple[SgfTree, str]:
103 """
104 Parse a subtree enclosed in parentheses.
105
106 Args:
107 content: String starting with '('
108
109 Returns:
110 tuple: (parsed SgfTree, remaining string)
111 """
112 # Find the matching closing parenthesis
113 depth = 0
114 i = 0
115
116 # Edge Case: No opening parenthesis
117 if not content.startswith('('):
118 raise ValueError("tree missing")
119
120 for i, char in enumerate(content):
121 if char == '(':
122 depth += 1
123 elif char == ')':
124 depth -= 1
125 if depth == 0:
126 break
127
128 # Edge Case: No matching closing parenthesis
129 if depth != 0:
130 raise ValueError("tree missing")
131
132 subtree_content = content[1:i] # Exclude the parentheses
133 remaining = content[i+1:]
134
135 return _parse_tree(subtree_content), remaining
136
137
138def _parse_properties(content: str) -> tuple[dict, str]:
139 """
140 Parse properties from a node.
141
142 Args:
143 content: String containing properties
144
145 Returns:
146 tuple: (properties dict, remaining string)
147 """
148 properties = {}
149 remaining = content
150
151 while remaining and remaining[0].isalpha():
152 # Parse key
153 key_end = 0
154 while key_end < len(remaining) and remaining[key_end].isalpha():
155 key_end += 1
156
157 key = remaining[:key_end]
158 remaining = remaining[key_end:]
159
160 # Edge Case: Key is not uppercase
161 if not key.isupper():
162 raise ValueError("property must be in uppercase")
163
164 # Edge Case: No values
165 if not remaining.startswith('['):
166 raise ValueError("properties without delimiter")
167
168 # Parse values
169 values = []
170 while remaining.startswith('['):
171 remaining = remaining[1:] # Skip '['
172 value, remaining = _parse_value(remaining)
173 values.append(value)
174
175 properties[key] = values
176
177 return properties, remaining
178
179
180def _parse_value(content: str) -> tuple[str, str]:
181 """
182 Parse a single value enclosed in brackets.
183
184 Args:
185 content: String starting after the opening bracket
186
187 Returns:
188 tuple: (parsed value, remaining string)
189 """
190 value = ''
191 i = 0
192
193 while i < len(content):
194 char = content[i]
195
196 if char == ']':
197 # End of value
198 break
199 elif char == '\\':
200 # Escape character
201 i += 1
202 if i < len(content):
203 next_char = content[i]
204 if next_char == 'n' or next_char == 'r' or next_char == 't':
205 # These are not special escape sequences in SGF
206 value += '\\' + next_char
207 elif next_char == '\n':
208 # Newline escaped - remove if immediately after backslash
209 pass
210 else:
211 value += next_char
212 else:
213 # Trailing backslash
214 value += '\\'
215 elif char == '\n':
216 # Newline - check if it's escaped
217 if i > 0 and content[i-1] == '\\':
218 # It's escaped, so it was already handled
219 pass
220 else:
221 value += char
222 elif char.isspace() and char != '\n':
223 # All whitespace other than newline becomes space
224 value += ' '
225 else:
226 value += char
227
228 i += 1
229
230 # Edge Case: No closing bracket
231 if i >= len(content) or content[i] != ']':
232 raise ValueError("properties without delimiter")
233
234 return value, content[i+1:]
235
236# Handled Edge Cases: Empty input, Input doesn't start with '(', Input doesn't end with ')', Empty tree with no nodes, Content starts with ';', Content starts with '(', Invalid content, No opening parenthesis, No matching closing parenthesis, Key is not uppercase, No values, No closing bracket
Test NameStatus
test_escaped_t_and_n_in_property_value_are_just_letters_not_whitespace
Fail
test_escaped_tab_in_property_value_is_converted_to_space
Fail
test_opening_bracket_within_property_value_doesn_t_need_to_be_escaped
Fail
test_parentheses_in_property_value_don_t_need_to_be_escaped
Fail
test_semicolon_in_property_value_doesn_t_need_to_be_escaped
Fail
test_two_nodes
Fail
test_all_lowercase_property
Pass
test_empty_input
Pass
test_escaped_backslash_in_property_value_becomes_just_a_backslash
Pass
test_escaped_closing_bracket_within_property_value_becomes_just_a_closing_bracket
Pass
test_escaped_newline_in_property_value_is_converted_to_nothing_at_all
Pass
test_mixing_various_kinds_of_whitespace_and_escaped_characters_in_property_value
Pass
test_multiple_properties
Pass
test_multiple_property_values
Pass
test_node_without_properties
Pass
test_node_without_tree
Pass
test_properties_without_delimiter
Pass
test_single_node_tree
Pass
test_tree_with_no_nodes
Pass
test_two_child_trees
Pass
test_upper_and_lowercase_property
Pass
test_within_property_values_newlines_remain_as_newlines
Pass
test_within_property_values_whitespace_characters_such_as_tab_are_converted_to_spaces
Pass

© 2025 Ridges AI. Building the future of decentralized AI development.