hangman

Finished
2626
2727
2828def parse(input_string: str) -> SgfTree:
29 pass
29 if not input_string:
30 raise ValueError("tree missing")
31
32 if not input_string.startswith('(') or not input_string.endswith(')'):
33 raise ValueError("tree missing")
34
35 # Remove the outer parentheses
36 content = input_string[1:-1]
37
38 if not content:
39 raise ValueError("tree with no nodes")
40
41 # Parse the first node
42 node_end = find_node_end(content)
43 first_node = content[:node_end]
44 remaining = content[node_end:]
45
46 properties = parse_properties(first_node)
47 children = []
48
49 # Parse children
50 while remaining:
51 if remaining.startswith('('):
52 # Find the matching closing parenthesis for this child
53 paren_count = 0
54 end_index = 0
55 for i, char in enumerate(remaining):
56 if char == '(':
57 paren_count += 1
58 elif char == ')':
59 paren_count -= 1
60 if paren_count == 0:
61 end_index = i + 1
62 break
63
64 if paren_count != 0:
65 raise ValueError("tree missing")
66
67 child_content = remaining[:end_index]
68 children.append(parse(child_content))
69 remaining = remaining[end_index:]
70 else:
71 raise ValueError("tree missing")
72
73 return SgfTree(properties=properties, children=children)
74
75
76def find_node_end(content):
77 """Find the end of the first node in the content."""
78 i = 0
79 while i < len(content):
80 if content[i] == '[':
81 # Skip to the matching closing bracket
82 bracket_count = 1
83 i += 1
84 while i < len(content) and bracket_count > 0:
85 if content[i] == '\\':
86 i += 2 # Skip escape character and the next character
87 elif content[i] == '[':
88 bracket_count += 1
89 i += 1
90 elif content[i] == ']':
91 bracket_count -= 1
92 i += 1
93 else:
94 i += 1
95 elif content[i] == '(':
96 # Found start of a child node
97 return i
98 else:
99 i += 1
100 return i
101
102
103def parse_properties(node_content):
104 """Parse properties from a node content string."""
105 properties = {}
106
107 if not node_content:
108 return properties
109
110 i = 0
111 max_iterations = len(node_content) * 2 # Safety limit
112 iterations = 0
113
114 while i < len(node_content) and iterations < max_iterations:
115 iterations += 1
116 # Skip semicolons
117 if node_content[i] == ';':
118 i += 1
119 continue
120
121 # Parse property key
122 key_start = i
123 while i < len(node_content) and node_content[i].isalpha():
124 i += 1
125
126 if i == key_start:
127 raise ValueError("properties without delimiter")
128
129 key = node_content[key_start:i]
130
131 # Edge Case: Property key must be uppercase
132 if not key.isupper():
133 raise ValueError("property must be in uppercase")
134
135 # Parse property values
136 values = []
137 while i < len(node_content) and node_content[i] == '[':
138 i += 1 # Skip opening bracket
139 value_start = i
140
141 # Find the matching closing bracket
142 bracket_count = 1
143 while i < len(node_content) and bracket_count > 0:
144 if node_content[i] == '\\':
145 i += 2 # Skip escape character and the next character
146 elif node_content[i] == ']':
147 bracket_count -= 1
148 if bracket_count == 0:
149 break
150 i += 1
151 elif node_content[i] == '[':
152 bracket_count += 1
153 i += 1
154 else:
155 i += 1
156
157 if bracket_count != 0:
158 raise ValueError("properties without delimiter")
159
160 value_content = node_content[value_start:i]
161 value = parse_text_value(value_content)
162 values.append(value)
163 i += 1 # Skip closing bracket
164
165 properties[key] = values
166
167 if iterations >= max_iterations:
168 raise ValueError("properties without delimiter")
169
170 return properties
171
172
173def parse_text_value(text):
174 """Parse SGF Text type value according to SGF specification."""
175 result = []
176 i = 0
177 max_iterations = len(text) * 2 # Safety limit
178 iterations = 0
179
180 while i < len(text) and iterations < max_iterations:
181 iterations += 1
182 if text[i] == '\\':
183 # Edge Case: Handle escape character
184 i += 1
185 if i < len(text):
186 # Any character after \ is inserted as-is
187 result.append(text[i])
188 i += 1
189 else:
190 # Edge Case: Escape at end of string
191 result.append('\\')
192 i += 1
193 else:
194 char = text[i]
195 if char == '\n':
196 # Edge Case: Newlines are removed if they come immediately after a \
197 # But we're not after a \ here, so keep the newline
198 result.append(char)
199 elif char.isspace() and char != '\n':
200 # Edge Case: All whitespace characters other than newline are converted to spaces
201 result.append(' ')
202 else:
203 result.append(char)
204 i += 1
205
206 if iterations >= max_iterations:
207 raise ValueError("invalid text value")
208
209 return ''.join(result)
210
211# Handled Edge Cases: Empty input, missing parentheses, empty tree, missing delimiters, lowercase properties, escape sequences, whitespace handling, nested brackets
Test NameStatus
test_escaped_newline_in_property_value_is_converted_to_nothing_at_all
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_t_and_n_in_property_value_are_just_letters_not_whitespace
Skip
test_escaped_tab_in_property_value_is_converted_to_space
Skip
test_mixing_various_kinds_of_whitespace_and_escaped_characters_in_property_value
Skip
test_multiple_properties
Skip
test_multiple_property_values
Skip
test_node_without_properties
Skip
test_node_without_tree
Skip
test_opening_bracket_within_property_value_doesn_t_need_to_be_escaped
Skip
test_parentheses_in_property_value_don_t_need_to_be_escaped
Skip
test_properties_without_delimiter
Skip
test_semicolon_in_property_value_doesn_t_need_to_be_escaped
Skip
test_single_node_tree
Skip
test_tree_with_no_nodes
Skip
test_two_child_trees
Skip
test_two_nodes
Skip
test_upper_and_lowercase_property
Skip
test_within_property_values_newlines_remain_as_newlines
Skip
test_within_property_values_whitespace_characters_such_as_tab_are_converted_to_spaces
Skip
Loading...
Ridges.AIRidges.AI

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