Finished
2626
2727
2828def parse(input_string: str) -> SgfTree:
29 pass
29 """Parse an SGF string and return a tree structure of properties."""
30 # Edge Case: Empty input
31 if not input_string:
32 raise ValueError("tree missing")
33
34 # Edge Case: Input doesn't start with '('
35 if not input_string.startswith('('):
36 raise ValueError("tree missing")
37
38 # Edge Case: Input doesn't end with ')'
39 if not input_string.endswith(')'):
40 raise ValueError("tree missing")
41
42 # Remove the outer parentheses
43 content = input_string[1:-1]
44
45 # Edge Case: Empty tree with no nodes
46 if not content:
47 raise ValueError("tree with no nodes")
48
49 # Parse the tree
50 tree, _ = _parse_tree("(" + input_string[1:-1] + ")", 0)
51 return tree
52
53
54def _parse_node(content: str, index: int) -> tuple[SgfTree, int]:
55 """Parse a node from the content starting at index."""
56 # Edge Case: Node doesn't start with ';'
57 if index >= len(content) or content[index] != ';':
58 raise ValueError("properties without delimiter")
59
60 index += 1 # Skip the ';'
61 properties = {}
62 children = []
63
64 # Parse properties
65 while index < len(content) and content[index] not in '();':
66 # Edge Case: Empty property (no key)
67 if index < len(content) and not content[index].isalpha():
68 raise ValueError("tree missing")
69
70 # Parse property key
71 key_start = index
72 while index < len(content) and content[index].isalpha():
73 index += 1
74
75 # Edge Case: Property key is not in uppercase
76 key = content[key_start:index]
77 if not key.isupper():
78 raise ValueError("property must be in uppercase")
79
80 # Edge Case: No property values
81 if index >= len(content) or content[index] != '[':
82 raise ValueError("properties without delimiter")
83
84 # Parse property values
85 values = []
86 while index < len(content) and content[index] == '[':
87 index += 1 # Skip '['
88 value, index = _parse_value(content, index)
89
90 # Debug print
91 print(f"After _parse_value: index={index}, content[index:]={repr(content[index:] if index < len(content) else "END")}")
92
93 # Edge Case: Missing closing ']'
94 if index >= len(content) or content[index] != ']':
95 raise ValueError("properties without delimiter")
96
97 index += 1 # Skip ']'
98 values.append(value)
99
100 properties[key] = values
101
102 # Parse children
103 while index < len(content) and content[index] == '(':
104 child, index = _parse_tree(content, index)
105 children.append(child)
106
107 return SgfTree(properties, children), index
108
109
110def _parse_tree(content: str, index: int) -> tuple[SgfTree, int]:
111 """Parse a tree (sequence of nodes) from the content starting at index."""
112 # Edge Case: Tree doesn't start with '('
113 if index >= len(content) or content[index] != '(':
114 raise ValueError("tree missing")
115
116 index += 1 # Skip '('
117
118 # Parse the first node
119 first_node, index = _parse_node(content, index)
120
121 # Handle linear sequences (shorthand)
122 current_tree = first_node
123 while index < len(content) and content[index] == ';':
124 # Check if there's a valid node after the semicolon
125 if index + 1 >= len(content) or content[index + 1] != ';':
126 # If we're at the end or the next character is not a semicolon, check if it's a valid node
127 try:
128 next_node, index = _parse_node(content, index)
129 current_tree.children.append(next_node)
130 current_tree = next_node
131 except ValueError:
132 # If parsing fails, it means we have a malformed tree
133 raise ValueError("tree missing")
134 else:
135 # This is a malformed tree
136 raise ValueError("tree missing")
137
138 # Parse variations (multiple children)
139 while index < len(content) and content[index] == '(':
140 child, index = _parse_tree(content, index)
141 first_node.children.append(child)
142
143 # Edge Case: Tree doesn't end with ')'
144 if index >= len(content) or content[index] != ')':
145 raise ValueError("tree missing")
146
147 index += 1 # Skip ')'
148 return first_node, index
149
150
151def _parse_value(content: str, index: int) -> tuple[str, int]:
152 """Parse a value from the content starting at index."""
153 print(f"_parse_value called with content={repr(content)}, index={index}")
154 result = []
155 while index < len(content) and content[index] != ']':
156 print(f" Loop: index={index}, char={repr(content[index])}")
157 if content[index] == '\\':
158 # Handle escape sequences
159 index += 1
160 if index >= len(content):
161 # Edge Case: Escape at end of value
162 result.append('\\')
163 break
164
165 char = content[index]
166 if char == '\n':
167 # Newline immediately after \ is removed
168 index += 1 # Skip the newline
169 elif char in ' \t\r':
170 # Whitespace after \ is converted to space
171 result.append(' ')
172 index += 1
173 else:
174 # Any other character after \ is inserted as-is
175 result.append(char)
176 index += 1
177 elif content[index] in ' \t\r':
178 # All whitespace other than newline is converted to space
179 result.append(' ')
180 elif content[index] == '\n':
181 # Newlines are kept as-is
182 result.append(content[index])
183 else:
184 result.append(content[index])
185 index += 1
186
187 return ''.join(result), index
188
189# Handled Edge Cases: Empty input, Input doesn't start with '(', Input doesn't end with ')', Empty tree with no nodes, Node doesn't start with ';', Property key is not in uppercase, No property values, Missing closing ']', Tree doesn't start with '(', Tree doesn't end with ')', Escape at end of value"
Test NameStatus
test_escaped_backslash_in_property_value_becomes_just_a_backslash
Fail
test_escaped_closing_bracket_within_property_value_becomes_just_a_closing_bracket
Fail
test_escaped_newline_in_property_value_is_converted_to_nothing_at_all
Fail
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_mixing_various_kinds_of_whitespace_and_escaped_characters_in_property_value
Fail
test_opening_bracket_within_property_value_doesn_t_need_to_be_escaped
Fail
test_all_lowercase_property
Pass
test_empty_input
Pass
test_multiple_properties
Pass
test_multiple_property_values
Pass
test_node_without_properties
Pass
test_node_without_tree
Pass
test_parentheses_in_property_value_don_t_need_to_be_escaped
Pass
test_properties_without_delimiter
Pass
test_semicolon_in_property_value_doesn_t_need_to_be_escaped
Pass
test_single_node_tree
Pass
test_tree_with_no_nodes
Pass
test_two_child_trees
Pass
test_two_nodes
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.