tofu

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 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 which starts the node
46 if not content.startswith(';'):
47 raise ValueError("tree with no nodes")
48
49 # Parse the first node
50 node_content, remaining = _parse_node_content(content[1:])
51 properties = _parse_properties(node_content)
52
53 children = []
54
55 # Parse children
56 while remaining:
57 if remaining.startswith('('):
58 # Parse a subtree
59 subtree_content, remaining = _extract_subtree(remaining)
60 children.append(_parse_tree(subtree_content))
61 elif remaining.startswith(';'):
62 # Parse a sequential child node
63 child_node = _parse_tree(remaining)
64 children.append(child_node)
65 break
66 else:
67 raise ValueError("tree missing")
68
69 return SgfTree(properties=properties, children=children)
70
71
72def _parse_node_content(content: str) -> tuple[str, str]:
73 # Extract properties until we hit a '(' or ';' or end of string
74 i = 0
75 while i < len(content):
76 if content[i] == '(' or content[i] == ';':
77 break
78 i += 1
79 return content[:i], content[i:]
80
81
82def _extract_subtree(content: str) -> tuple[str, str]:
83 if not content.startswith('('):
84 raise ValueError("tree missing")
85
86 # Find matching parenthesis
87 level = 0
88 for i, char in enumerate(content):
89 if char == '(':
90 level += 1
91 elif char == ')':
92 level -= 1
93 if level == 0:
94 return content[1:i], content[i+1:]
95
96 raise ValueError("tree missing")
97
98
99def _parse_properties(content: str) -> dict:
100 properties = {}
101 i = 0
102
103 while i < len(content):
104 # Parse key
105 if not content[i].isalpha() or not content[i].isupper():
106 raise ValueError("property must be in uppercase")
107
108 key_start = i
109 while i < len(content) and content[i].isalpha() and content[i].isupper():
110 i += 1
111
112 if i == key_start:
113 raise ValueError("property must be in uppercase")
114
115 key = content[key_start:i]
116
117 # Check if key already exists
118 if key in properties:
119 raise ValueError("properties without delimiter")
120
121 # Parse values
122 values = []
123 while i < len(content) and content[i] == '[':
124 value, i = _parse_value(content, i)
125 values.append(value)
126
127 if not values:
128 raise ValueError("properties without delimiter")
129
130 properties[key] = values
131
132 return properties
133
134
135def _parse_value(content: str, start: int) -> tuple[str, int]:
136 if content[start] != '[':
137 raise ValueError("properties without delimiter")
138
139 i = start + 1
140 value = []
141
142 while i < len(content) and content[i] != ']':
143 if content[i] == '\\':
144 # Handle escape character
145 i += 1
146 if i >= len(content):
147 raise ValueError("properties without delimiter")
148
149 # If next character is newline, skip both \ and newline
150 if content[i] == '\n':
151 i += 1
152 else:
153 # Any non-whitespace character after \ is inserted as-is
154 # Any whitespace character after \ is converted according to rules
155 char = content[i]
156 if char.isspace() and char != '\n':
157 value.append(' ')
158 else:
159 value.append(char)
160 i += 1
161 else:
162 # Regular character
163 char = content[i]
164 if char == '\n':
165 value.append(char)
166 elif char.isspace():
167 value.append(' ')
168 else:
169 value.append(char)
170 i += 1
171
172 if i >= len(content) or content[i] != ']':
173 raise ValueError("properties without delimiter")
174
175 return ''.join(value), i + 1
Test NameStatus
test_parentheses_in_property_value_don_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_opening_bracket_within_property_value_doesn_t_need_to_be_escaped
Pass
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.