python

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 return _parse_tree(content)
42
43
44def _parse_tree(content: str) -> SgfTree:
45 # Find the first semicolon to separate the root node from children
46 if not content.startswith(';'):
47 # Check if it's a node without semicolon (should not happen in valid SGF but let's be safe)
48 raise ValueError("tree with no nodes")
49
50 # Skip the first semicolon
51 content = content[1:]
52
53 # Parse the root node properties
54 properties, remaining = _parse_properties(content)
55
56 children = []
57
58 # Parse children
59 while remaining.startswith('('):
60 # Find matching closing parenthesis
61 balance = 1
62 i = 1
63 while i < len(remaining) and balance > 0:
64 if remaining[i] == '(':
65 balance += 1
66 elif remaining[i] == ')':
67 balance -= 1
68 i += 1
69
70 if balance != 0:
71 raise ValueError("tree missing")
72
73 # Extract child subtree
74 child_content = remaining[1:i-1]
75 children.append(_parse_tree(child_content))
76
77 # Move to the next potential child
78 remaining = remaining[i:]
79
80 return SgfTree(properties=properties, children=children)
81
82
83def _parse_properties(content: str) -> tuple[dict, str]:
84 properties = {}
85 i = 0
86
87 while i < len(content) and content[i] != '(' and content[i] != ')':
88 # Parse property key
89 if not content[i].isalpha() or not content[i].isupper():
90 if content[i] == ';':
91 # Next node, stop parsing properties
92 break
93 raise ValueError("property must be in uppercase")
94
95 key_start = i
96 while i < len(content) and content[i].isalpha() and content[i].isupper():
97 i += 1
98
99 key = content[key_start:i]
100
101 # Edge Case: Property without values
102 if i >= len(content) or content[i] != '[':
103 raise ValueError("properties without delimiter")
104
105 # Parse values
106 values = []
107 while i < len(content) and content[i] == '[':
108 i += 1 # Skip opening bracket
109 value_start = i
110
111 # Find closing bracket, handling escaped brackets
112 bracket_count = 1
113 while i < len(content) and bracket_count > 0:
114 if content[i] == '\\' and i + 1 < len(content):
115 i += 2 # Skip escape character and the next character
116 elif content[i] == '[':
117 bracket_count += 1
118 i += 1
119 elif content[i] == ']':
120 bracket_count -= 1
121 i += 1
122 else:
123 i += 1
124
125 # Edge Case: Unclosed bracket
126 if bracket_count != 0:
127 raise ValueError("properties without delimiter")
128
129 value = content[value_start:i-1]
130 values.append(_unescape_text(value))
131
132 properties[key] = values
133
134 # Skip any whitespace
135 while i < len(content) and content[i].isspace():
136 i += 1
137
138 return properties, content[i:]
139
140
141def _unescape_text(text: str) -> str:
142 result = []
143 i = 0
144 while i < len(text):
145 if text[i] == '\\':
146 # Edge Case: Escape at end of string
147 if i + 1 >= len(text):
148 result.append('\\')
149 break
150 next_char = text[i + 1]
151 # Handle newline removal after backslash
152 if next_char == '\n':
153 # Remove the backslash and newline
154 i += 2
155 continue
156 # Handle other escaped characters
157 if next_char.isspace():
158 # Convert whitespace to space, but respect the backslash removal rule for newlines
159 result.append(' ')
160 i += 2
161 else:
162 # Insert non-whitespace character as-is
163 result.append(next_char)
164 i += 2
165 else:
166 # Convert other whitespace to spaces
167 if text[i] != '\n' and text[i].isspace():
168 result.append(' ')
169 else:
170 result.append(text[i])
171 i += 1
172
173 return ''.join(result)
174
175# Handled Edge Cases: Empty input, missing parentheses, no nodes, property not uppercase, missing delimiters, unclosed brackets, escape at end of string
Test NameStatus
test_opening_bracket_within_property_value_doesn_t_need_to_be_escaped
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_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.