great

Finished
2626
2727
2828def parse(input_string: str) -> SgfTree:
29 pass
29 """
30 Parse an SGF (Smart Game Format) string and return a tree structure of properties.
31
32 Args:
33 input_string: A string containing SGF formatted data
34
35 Returns:
36 SgfTree: A tree structure representing the parsed SGF data
37
38 Raises:
39 ValueError: If the input is invalid according to SGF format rules
40 """
41 # Edge Case: Empty input or input that doesn't start with '('
42 if not input_string or not input_string.startswith('('):
43 raise ValueError("tree missing")
44
45 # Edge Case: Input that starts with '(' but doesn't end with ')'
46 if not input_string.endswith(')'):
47 raise ValueError("tree missing")
48
49 # Remove the outer parentheses
50 content = input_string[1:-1]
51
52 # Edge Case: Empty content after removing parentheses
53 if not content:
54 raise ValueError("tree with no nodes")
55
56 # Edge Case: Content that doesn't start with ';'
57 if not content.startswith(';'):
58 raise ValueError("tree missing")
59
60 return _parse_tree(content)
61
62
63def _parse_tree(content: str) -> SgfTree:
64 """
65 Parse a tree from SGF content.
66
67 Args:
68 content: SGF content starting with ';'
69
70 Returns:
71 SgfTree: Parsed tree
72 """
73 # Skip the initial ';'
74 i = 1
75
76 # Parse properties
77 properties = {}
78 while i < len(content) and content[i].isupper():
79 # Parse key
80 key_start = i
81 while i < len(content) and content[i].isupper():
82 i += 1
83
84 key = content[key_start:i]
85
86 # Edge Case: Property key is not in uppercase (this should not happen given the while condition,
87 # but we check for completeness)
88 if not key.isupper():
89 raise ValueError("property must be in uppercase")
90
91 # Parse values
92 values = []
93
94 # Edge Case: No values after key
95 if i >= len(content) or content[i] != '[':
96 raise ValueError("properties without delimiter")
97
98 while i < len(content) and content[i] == '[':
99 i += 1 # Skip '['
100 value_start = i
101
102 # Parse value, handling escapes
103 while i < len(content) and content[i] != ']':
104 if content[i] == '\\':
105 i += 2 # Skip escape and next character
106 else:
107 i += 1
108
109 # Edge Case: Unclosed value bracket
110 if i >= len(content) or content[i] != ']':
111 raise ValueError("properties without delimiter")
112
113 value = content[value_start:i]
114 values.append(_unescape_text(value))
115 i += 1 # Skip ']'
116
117 properties[key] = values
118
119 # Parse children
120 children = []
121
122 while i < len(content) and content[i] == '(':
123 # Find matching parenthesis
124 paren_count = 1
125 start = i + 1
126 i += 1
127
128 while i < len(content) and paren_count > 0:
129 if content[i] == '(':
130 paren_count += 1
131 elif content[i] == ')':
132 paren_count -= 1
133 i += 1
134
135 # Edge Case: Unmatched parenthesis
136 if paren_count != 0:
137 raise ValueError("tree missing")
138
139 # Extract child content (excluding the closing parenthesis)
140 child_content = content[start:i-1]
141
142 # Edge Case: Empty child content
143 if not child_content:
144 raise ValueError("tree with no nodes")
145
146 children.append(_parse_tree(child_content))
147
148 return SgfTree(properties, children)
149
150
151def _unescape_text(text: str) -> str:
152 """
153 Unescape SGF text according to SGF specification.
154
155 Args:
156 text: Escaped SGF text
157
158 Returns:
159 str: Unescaped text
160 """
161 result = []
162 i = 0
163 while i < len(text):
164 if text[i] == '\\':
165 i += 1
166 if i < len(text):
167 # If next character is newline, remove both \ and newline
168 if text[i] == '\n':
169 i += 1
170 # Remove any following whitespace until non-whitespace
171 while i < len(text) and text[i].isspace() and text[i] != '\n':
172 i += 1
173 continue
174 else:
175 # Any non-whitespace character after \ is inserted as-is
176 # Any whitespace character after \ follows the rules (converted to space)
177 if text[i].isspace() and text[i] != '\n':
178 result.append(' ')
179 else:
180 result.append(text[i])
181 i += 1
182 else:
183 # All whitespace characters other than newline are converted to spaces
184 if text[i].isspace() and text[i] != '\n':
185 result.append(' ')
186 else:
187 result.append(text[i])
188 i += 1
189
190 # Join and then normalize newlines (remove \ followed by newline)
191 return ''.join(result)
192# Handled Edge Cases: Empty input, missing opening parenthesis, missing closing parenthesis,
193# empty content after parentheses removal, missing semicolon, property key not uppercase,
194# no values after key, unclosed value bracket, unmatched parenthesis, empty child content
Test NameStatus
test_all_lowercase_property
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_upper_and_lowercase_property
Fail
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_escaped_t_and_n_in_property_value_are_just_letters_not_whitespace
Pass
test_escaped_tab_in_property_value_is_converted_to_space
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_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.