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 # Parse the tree
45 tree, _ = _parse_tree(content, 0)
46 return tree
47
48
49def _parse_tree(content: str, index: int) -> tuple[SgfTree, int]:
50 # Edge Case: No semicolon to start a node
51 if index >= len(content) or content[index] != ';':
52 raise ValueError("tree with no nodes")
53
54 index += 1 # Skip the semicolon
55
56 # Parse properties of the current node
57 properties = {}
58 while index < len(content) and content[index] not in '();':
59 # Parse key
60 key_start = index
61 while index < len(content) and content[index].isalpha() and content[index].isupper():
62 index += 1
63
64 # Edge Case: Key is not all uppercase or empty
65 if index == key_start:
66 raise ValueError("property must be in uppercase")
67
68 key = content[key_start:index]
69
70 # Parse values
71 values = []
72 while index < len(content) and content[index] == '[':
73 index += 1 # Skip '['
74 value_start = index
75
76 # Parse value with escape sequences
77 value_chars = []
78 while index < len(content) and content[index] != ']':
79 if content[index] == '\\':
80 index += 1
81 if index >= len(content):
82 raise ValueError("properties without delimiter")
83
84 char = content[index]
85 if char == 'n' or char == 'r' or char == 't':
86 # These are not escape sequences in SGF, just literal characters
87 value_chars.append('\\')
88 value_chars.append(char)
89 else:
90 value_chars.append(char)
91 else:
92 value_chars.append(content[index])
93 index += 1
94
95 # Edge Case: Missing closing bracket
96 if index >= len(content) or content[index] != ']':
97 raise ValueError("properties without delimiter")
98
99 value = ''.join(value_chars)
100 # Process according to SGF Text type rules
101 processed_value = _process_sgf_text(value)
102 values.append(processed_value)
103 index += 1 # Skip ']'
104
105 # Edge Case: No values for the property
106 if not values:
107 raise ValueError("properties without delimiter")
108
109 properties[key] = values
110
111 # Parse children
112 children = []
113 while index < len(content) and content[index] == '(':
114 child, index = _parse_tree(content, index+1) # Skip '('
115 children.append(child)
116 # Skip the closing ')'
117 if index >= len(content) or content[index] != ')':
118 raise ValueError("tree missing")
119 index += 1
120
121 return SgfTree(properties, children), index
122
123
124def _process_sgf_text(text: str) -> str:
125 """Process SGF Text according to the rules"""
126 result = []
127 i = 0
128 while i < len(text):
129 if text[i] == '\\':
130 i += 1
131 if i < len(text):
132 char = text[i]
133 if char == 'n':
134 # \n at end of line removes the newline
135 if i+1 < len(text) and text[i+1] == '\n':
136 i += 1 # Skip the actual newline
137 # If \n is not at end of line, it's just an 'n'
138 else:
139 result.append('n')
140 elif char == 'r' or char == 't':
141 # These are not escape sequences in SGF
142 result.append('\\')
143 result.append(char)
144 elif char == '\n':
145 # Escaped newline at end of line removes the newline
146 pass
147 elif char.isspace():
148 # Any whitespace after \ is inserted as-is (but whitespace rules still apply)
149 result.append(char)
150 else:
151 # Any non-whitespace character after \ is inserted as-is
152 result.append(char)
153 else:
154 # Trailing backslash
155 result.append('\\')
156 else:
157 char = text[i]
158 if char == '\n':
159 result.append(char)
160 elif char.isspace() and char != '\n':
161 result.append(' ')
162 else:
163 result.append(char)
164 i += 1
165
166 # Join and then handle newlines that come immediately after \
167 s = ''.join(result)
168 # Remove newlines that come immediately after a backslash
169 # This is a post-processing step
170 processed = []
171 i = 0
172 while i < len(s):
173 if s[i] == '\\' and i + 1 < len(s) and s[i+1] == '\n':
174 i += 2 # Skip both \ and \n
175 # Also skip any actual newlines that might follow
176 while i < len(s) and s[i] == '\n':
177 i += 1
178 else:
179 processed.append(s[i])
180 i += 1
181
182 return ''.join(processed)
183# Handled Edge Cases: Empty input, missing opening parenthesis, missing closing parenthesis, empty content after parentheses, missing semicolon, key not uppercase, missing closing bracket, no values for property, missing tree structure elements
Test NameStatus
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_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_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_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_tab_in_property_value_is_converted_to_space
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.