awesome

Finished
2626
2727
2828def parse(input_string: str) -> SgfTree:
29 pass
29 # Edge Case: Empty input or doesn't start with '('
30 if not input_string or input_string[0] != '(':
31 raise ValueError("tree missing")
32
33 # Edge Case: Doesn't end with ')'
34 if input_string[-1] != ')':
35 raise ValueError("tree missing")
36
37 # Remove the outer parentheses
38 content = input_string[1:-1]
39
40 # Edge Case: Empty content after removing parentheses
41 if not content:
42 raise ValueError("tree with no nodes")
43
44 # Edge Case: Doesn't start with ';'
45 if content[0] != ';':
46 raise ValueError("tree with no nodes")
47
48 return _parse_tree(content)
49
50
51def _parse_tree(content: str) -> SgfTree:
52 # Find the first node
53 if not content or content[0] != ';':
54 raise ValueError("tree with no nodes")
55
56 # Parse the first node
57 node_content, remaining = _parse_node(content[1:])
58
59 # Check for children
60 children = []
61 while remaining:
62 if remaining[0] == '(':
63 # Parse a subtree
64 subtree_content, remaining = _parse_subtree(remaining)
65 children.append(_parse_tree(subtree_content))
66 else:
67 # No more children
68 break
69
70 return SgfTree(properties=node_content, children=children)
71
72
73def _parse_subtree(content: str) -> tuple[str, str]:
74 if not content or content[0] != '(':
75 raise ValueError("tree missing")
76
77 depth = 1
78 i = 1
79 while i < len(content) and depth > 0:
80 if content[i] == '(':
81 depth += 1
82 elif content[i] == ')':
83 depth -= 1
84 i += 1
85
86 # Edge Case: Unmatched parentheses
87 if depth != 0:
88 raise ValueError("tree missing")
89
90 return content[1:i-1], content[i:]
91
92
93def _parse_node(content: str) -> tuple[dict, str]:
94 properties = {}
95 i = 0
96
97 while i < len(content):
98 # Check if we've reached the end of the node or start of children
99 if content[i] in ';(':
100 break
101
102 # Parse property key
103 key_start = i
104 while i < len(content) and content[i].isalpha():
105 i += 1
106
107 # Edge Case: No key found
108 if i == key_start:
109 raise ValueError("properties without delimiter")
110
111 key = content[key_start:i]
112
113 # Edge Case: Key is not uppercase
114 if key != key.upper():
115 raise ValueError("property must be in uppercase")
116
117 # Parse values
118 values = []
119 while i < len(content) and content[i] == '[':
120 value, i = _parse_value(content, i)
121 values.append(value)
122
123 # Edge Case: No values found for key
124 if not values:
125 raise ValueError("properties without delimiter")
126
127 properties[key] = values
128
129 return properties, content[i:]
130
131
132def _parse_value(content: str, start: int) -> tuple[str, int]:
133 # Edge Case: Doesn't start with '['
134 if start >= len(content) or content[start] != '[':
135 raise ValueError("properties without delimiter")
136
137 i = start + 1
138 value_chars = []
139
140 while i < len(content) and content[i] != ']':
141 if content[i] == '\\':
142 # Handle escape character
143 i += 1
144 # Edge Case: Escape at end of content
145 if i >= len(content):
146 raise ValueError("properties without delimiter")
147
148 char = content[i]
149 if char == 'n':
150 # Special case: \n should be treated as literal 'n' if not followed by newline
151 # But according to SGF spec, newlines are removed if they come immediately after \
152 # However, the problem says "Any non-whitespace character after \ is inserted as-is"
153 # and "Any whitespace character after \ follows the above rules"
154 # Since 'n' is not whitespace, it should be inserted as-is
155 value_chars.append('n')
156 elif char == 't':
157 # 't' is not whitespace, so insert as-is
158 value_chars.append('t')
159 elif char == 'r':
160 # 'r' is not whitespace, so insert as-is
161 value_chars.append('r')
162 elif char.isspace():
163 # Whitespace after \ follows special rules
164 if char == '\n':
165 # Newlines are removed if they come immediately after \
166 pass
167 else:
168 # Other whitespace is converted to space
169 value_chars.append(' ')
170 else:
171 # Non-whitespace character inserted as-is
172 value_chars.append(char)
173 elif content[i] == '\n':
174 # Newlines remain as newlines unless escaped
175 value_chars.append('\n')
176 elif content[i].isspace() and content[i] != '\n':
177 # All whitespace other than newline is converted to space
178 value_chars.append(' ')
179 else:
180 # Regular character
181 value_chars.append(content[i])
182 i += 1
183
184 # Edge Case: Unclosed bracket
185 if i >= len(content) or content[i] != ']':
186 raise ValueError("properties without delimiter")
187
188 return ''.join(value_chars), i + 1
189# Handled Edge Cases: Empty input, missing parentheses, empty content, missing semicolon, unmatched parentheses, no key found, key not uppercase, no values found, doesn't start with bracket, escape at end, unclosed bracket
Test NameStatus
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_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_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.