comparison src/player.cc @ 32:93224b26a0ee default tip

player: efforts towards C-ization
author Paper <paper@tflc.us>
date Mon, 10 Feb 2025 19:17:29 -0500
parents 973734ebd2be
children
comparison
equal deleted inserted replaced
31:668f4f31ddda 32:93224b26a0ee
3 3
4 #include <map> 4 #include <map>
5 #include <sstream> 5 #include <sstream>
6 #include <string> 6 #include <string>
7 #include <vector> 7 #include <vector>
8 #include <optional>
9 8
10 #include <iostream> 9 #include <iostream>
11 10
12 namespace animone { 11 #include <string.h>
13 12 #include <stdio.h>
14 namespace internal::parser { 13
15 14 // simple crc32-hash function, derived from Hacker's Delight
16 struct State { 15 // and expects a C-string as input.
17 enum class Name { 16 // `n` should be the amount of the largest expected value of
18 ExpectPlayerName, 17 // the hash.
19 ExpectSection, 18 static inline constexpr uint32_t crcn32b(unsigned char *message, size_t n)
20 ExpectWindowPlatform, 19 {
21 ExpectExecutablePlatform, 20 uint32_t crc = 0xFFFFFFFF;
22 ExpectWindow, 21 size_t i = 0, j = 0;
23 ExpectExecutable, 22
24 ExpectStrategy, 23 for (i = 0; message[i] && i < n; i++) {
25 ExpectType, 24 crc = crc ^ message[i];
26 ExpectWindowTitle, 25 for (j = 0; j < 8; j++)
27 }; 26 crc = (crc >> 1) ^ (0xEDB88320 & -(crc & 1));
28 27 }
29 Name state = Name::ExpectPlayerName; 28
30 WindowPlatform window_platform = WindowPlatform::Unknown; 29 return ~crc;
31 ExecutablePlatform executable_platform = ExecutablePlatform::Unknown; 30 }
32 }; 31
33 32 typedef enum StateName {
34 size_t GetIndentation(const std::string& line) { 33 STATENAME_EXPECTPLAYERNAME = 0,
35 return line.find_first_not_of('\t'); 34 STATENAME_EXPECTSECTION,
36 } 35 STATENAME_EXPECTWINDOWPLATFORM,
37 36 STATENAME_EXPECTEXECUTABLEPLATFORM,
38 bool HandleIndentation(const size_t current, const std::vector<Player>& players, State& state) { 37 STATENAME_EXPECTWINDOW,
38 STATENAME_EXPECTEXECUTABLE,
39 STATENAME_EXPECTSTRATEGY,
40 STATENAME_EXPECTTYPE,
41 STATENAME_EXPECTWINDOWTITLE,
42 } StateName;
43
44 typedef struct State {
45 StateName state;
46
47 animone::WindowPlatform window_platform;
48 animone::ExecutablePlatform executable_platform;
49 } State;
50
51 static size_t GetIndentation(const char *line) {
52 return strcspn(line, "\t");
53 }
54
55 static int HandleIndentation(const size_t current, const std::vector<animone::Player>& players, State *state) {
39 // Each state has a definitive expected indentation 56 // Each state has a definitive expected indentation
40 const auto expected = [](const State::Name& state) -> size_t { 57 size_t expected;
41 switch (state) { 58
42 default: 59 switch (state->state) {
43 case State::Name::ExpectPlayerName: return 0; 60 default:
44 case State::Name::ExpectSection: return 1; 61 case STATENAME_EXPECTPLAYERNAME:
45 case State::Name::ExpectWindowPlatform: 62 expected = 0;
46 case State::Name::ExpectExecutablePlatform: 63 break;
47 case State::Name::ExpectStrategy: 64 case STATENAME_EXPECTSECTION:
48 case State::Name::ExpectType: return 2; 65 expected = 1;
49 case State::Name::ExpectWindow: 66 break;
50 case State::Name::ExpectExecutable: 67 case STATENAME_EXPECTWINDOWPLATFORM:
51 case State::Name::ExpectWindowTitle: return 3; 68 case STATENAME_EXPECTEXECUTABLEPLATFORM:
52 } 69 case STATENAME_EXPECTSTRATEGY:
53 }(state.state); 70 case STATENAME_EXPECTTYPE:
71 expected = 2;
72 break;
73 case STATENAME_EXPECTWINDOW:
74 case STATENAME_EXPECTEXECUTABLE:
75 case STATENAME_EXPECTWINDOWTITLE:
76 expected = 3;
77 break;
78 }
54 79
55 if (current > expected) { 80 if (current > expected) {
56 std::cerr << "animone: excessive indentation found" << std::endl; 81 std::cerr << "animone: excessive indentation found" << std::endl;
57 return false; // Disallow excessive indentation 82 return false; // Disallow excessive indentation
58 } 83 }
59 84
60 if (current < expected) { 85 if (current < expected) {
61 const std::optional<State::Name> st = [current, state]() -> std::optional<State::Name> { 86 StateName name;
62 switch (current) { 87
63 case 0: return State::Name::ExpectPlayerName; 88 switch (current) {
64 default: 89 case 0: name = STATENAME_EXPECTPLAYERNAME; break;
65 case 1: return State::Name::ExpectSection; 90 default:
66 case 2: 91 case 1: name = STATENAME_EXPECTSECTION; break;
67 switch (state.state) { 92 case 2:
68 case State::Name::ExpectWindow: return State::Name::ExpectWindowPlatform; 93 switch (state->state) {
69 case State::Name::ExpectExecutable: return State::Name::ExpectExecutablePlatform; 94 case STATENAME_EXPECTWINDOW: name = STATENAME_EXPECTWINDOWPLATFORM; break;
70 default: return std::nullopt; 95 case STATENAME_EXPECTEXECUTABLE: name = STATENAME_EXPECTEXECUTABLEPLATFORM; break;
71 } 96 default: return 0;
72 } 97 }
73 }(); 98 break;
74 if (!st.has_value()) 99 }
75 return false; 100
76 101 switch (state->state) {
77 switch (state.state) { 102 case STATENAME_EXPECTWINDOW:
78 case State::Name::ExpectWindow: 103 if (players.back().windows.empty())
79 if (players.back().windows.empty()) 104 return 0;
80 return false; 105 state->state = name;
81 state.state = st.value(); 106 break;
82 break; 107 case STATENAME_EXPECTEXECUTABLE:
83 case State::Name::ExpectExecutable: 108 if (players.back().executables.empty())
84 if (players.back().executables.empty()) 109 return 0;
85 return false; 110 state->state = name;
86 state.state = st.value(); 111 break;
87 break; 112 case STATENAME_EXPECTSTRATEGY:
88 case State::Name::ExpectStrategy: 113 if (players.back().strategies.empty())
89 if (players.back().strategies.empty()) 114 return 0;
90 return false; 115 state->state = name;
91 state.state = st.value(); 116 break;
92 break; 117 case STATENAME_EXPECTTYPE:
93 case State::Name::ExpectType: 118 state->state = name;
94 state.state = st.value(); 119 break;
95 break; 120 case STATENAME_EXPECTWINDOWTITLE:
96 case State::Name::ExpectWindowTitle: 121 return 0;
97 return false; 122 default:
98 } 123 break; // ???
99 } 124 }
100 125 }
101 return true; 126
102 } 127 return 1;
103 128 }
104 bool HandleState(std::string& line, std::vector<Player>& players, State& state) { 129
105 switch (state.state) { 130 bool HandleState(char *line, std::vector<animone::Player>& players, State *state) {
106 case State::Name::ExpectPlayerName: 131 switch (state->state) {
107 players.push_back(Player()); 132 case STATENAME_EXPECTPLAYERNAME:
108 players.back().name = line; 133 players.push_back(animone::Player());
109 state.state = State::Name::ExpectSection; 134 players.back().name = line;
110 break; 135 state->state = STATENAME_EXPECTSECTION;
111 136 break;
112 case State::Name::ExpectSection: { 137
113 static const std::map<std::string, State::Name> sections = { 138 case STATENAME_EXPECTSECTION:
114 {"windows", State::Name::ExpectWindowPlatform}, 139 animone_internal_util_TrimRight(line, ":");
115 {"executables", State::Name::ExpectExecutablePlatform}, 140
116 {"strategies", State::Name::ExpectStrategy }, 141 switch (crcn32b((unsigned char *)line, 11)) { // max: "executables"
117 {"type", State::Name::ExpectType }, 142 case 0xe3e7859b: state->state = STATENAME_EXPECTWINDOWPLATFORM; break; // "windows"
118 }; 143 case 0x6cdf7147: state->state = STATENAME_EXPECTEXECUTABLEPLATFORM; break; // "executables"
119 util::TrimRight(line, ":"); 144 case 0x611f2213: state->state = STATENAME_EXPECTSTRATEGY; break; // "strategies"
120 const auto it = sections.find(line); 145 case 0x8cde5729: state->state = STATENAME_EXPECTTYPE; break; // "type"
121 if (it == sections.end()) 146 default: return 0;
122 return false; 147 }
123 state.state = it->second; 148
124 break; 149 break;
125 } 150
126 151 case STATENAME_EXPECTWINDOWPLATFORM:
127 case State::Name::ExpectWindowPlatform: { 152 animone_internal_util_TrimRight(line, ":");
128 static const std::map<std::string, WindowPlatform> platforms = { 153
129 {"quartz", WindowPlatform::Quartz}, 154 switch (crcn32b((unsigned char *)line, 6)) { // max: "quartz"
130 {"win32", WindowPlatform::Win32}, 155 case 0x6fe218f0: state->window_platform = animone::WindowPlatform::Quartz; break; // "quartz"
131 {"x11", WindowPlatform::X11}, 156 case 0xb50ba1d1: state->window_platform = animone::WindowPlatform::Win32; break; // "win32"
132 }; 157 case 0x3220e772: state->window_platform = animone::WindowPlatform::X11; break; // "x11"
133 util::TrimRight(line, ":"); 158 default: return 0;
134 const auto it = platforms.find(line); 159 }
135 if (it == platforms.end()) 160
136 return false; 161 state->state = STATENAME_EXPECTWINDOW;
137 state.state = State::Name::ExpectWindow; 162 break;
138 state.window_platform = it->second; 163
139 break; 164 case STATENAME_EXPECTEXECUTABLEPLATFORM:
140 } 165 animone_internal_util_TrimRight(line, ":");
141 166
142 case State::Name::ExpectExecutablePlatform: { 167 switch (crcn32b((unsigned char *)line, 6)) { // max: "macosx"
143 static const std::map<std::string, ExecutablePlatform> platforms = { 168 case 0xe0e30f6e: state->executable_platform = animone::ExecutablePlatform::Posix; break; // "posix"
144 {"posix", ExecutablePlatform::Posix}, 169 case 0xb50ba1d1: state->executable_platform = animone::ExecutablePlatform::Win32; break; // "win32"
145 {"win32", ExecutablePlatform::Win32}, 170 case 0x912ee411: state->executable_platform = animone::ExecutablePlatform::Xnu; break; // "macosx"
146 {"macosx", ExecutablePlatform::Xnu}, 171 default: return 0;
147 }; 172 }
148 util::TrimRight(line, ":"); 173
149 const auto it = platforms.find(line); 174 state->state = STATENAME_EXPECTEXECUTABLE;
150 if (it == platforms.end()) 175 break;
151 return false; 176
152 state.state = State::Name::ExpectExecutable; 177 case STATENAME_EXPECTWINDOW:
153 state.executable_platform = it->second; 178 players.back().windows[state->window_platform].push_back(line);
154 break; 179 break;
155 } 180
156 181 case STATENAME_EXPECTEXECUTABLE:
157 case State::Name::ExpectWindow: players.back().windows[state.window_platform].push_back(line); break; 182 players.back().executables[state->executable_platform].push_back(line);
158 183 break;
159 case State::Name::ExpectExecutable: players.back().executables[state.executable_platform].push_back(line); break; 184
160 185 case STATENAME_EXPECTSTRATEGY: {
161 case State::Name::ExpectStrategy: { 186 /* XXX PORT: These should be bit flags instead. */
162 static const std::map<std::string, Strategy> strategies = { 187 animone_internal_util_TrimRight(line, ":");
163 {"window_title", Strategy::WindowTitle }, 188
164 {"open_files", Strategy::OpenFiles }, 189 animone::Strategy strategy;
165 {"ui_automation", Strategy::UiAutomation}, 190
166 }; 191 switch (crcn32b((unsigned char *)line, 13)) { // max: "ui_automation"
167 util::TrimRight(line, ":"); 192 case 0xfef78b24: strategy = animone::Strategy::WindowTitle; break; // "window_title"
168 const auto it = strategies.find(line); 193 case 0x4d88605f: strategy = animone::Strategy::OpenFiles; break; // "open_files"
169 if (it == strategies.end()) 194 case 0xdceb02f6: strategy = animone::Strategy::UiAutomation; break; // "ui_automation"
170 return false; 195 default: return 0;
171 const auto strategy = it->second; 196 }
172 players.back().strategies.push_back(strategy); 197
173 switch (strategy) { 198 players.back().strategies.push_back(strategy);
174 case Strategy::WindowTitle: state.state = State::Name::ExpectWindowTitle; break; 199
175 } 200 switch (strategy) {
176 break; 201 case animone::Strategy::WindowTitle:
177 } 202 state->state = STATENAME_EXPECTWINDOWTITLE;
178 203 break;
179 case State::Name::ExpectType: { 204 default:
180 static const std::map<std::string, PlayerType> types = { 205 break;
181 {"default", PlayerType::Default }, 206 }
182 {"web_browser", PlayerType::WebBrowser}, 207 break;
183 }; 208 }
184 const auto it = types.find(line); 209
185 if (it == types.end()) 210 case STATENAME_EXPECTTYPE: {
186 return false; 211 switch (crcn32b((unsigned char *)line, 13)) { // max: "ui_automation"
187 players.back().type = it->second; 212 case 0xe35e00df: players.back().type = animone::PlayerType::Default; break; // "default"
188 break; 213 case 0x32bca1a4: players.back().type = animone::PlayerType::WebBrowser; break; // "web_browser"
189 } 214 default: return 0;
190 215 }
191 case State::Name::ExpectWindowTitle: 216
192 players.back().window_title_format = line; 217 break;
193 state.state = State::Name::ExpectStrategy; 218 }
194 break; 219
195 } 220 case STATENAME_EXPECTWINDOWTITLE:
196 221 players.back().window_title_format = line;
197 return true; 222 state->state = STATENAME_EXPECTSTRATEGY;
198 } 223 break;
199 224 }
200 } // namespace internal::parser 225
226 return 1;
227 }
201 228
202 //////////////////////////////////////////////////////////////////////////////// 229 ////////////////////////////////////////////////////////////////////////////////
203 230
204 bool ParsePlayersData(const std::string& data, std::vector<Player>& players) { 231 int animone_ParsePlayersData(const char *data, std::vector<animone::Player>& players) {
205 if (data.empty()) 232 if (!data || !*data)
206 return false; 233 return 0;
207 234
208 std::istringstream stream(data); 235 const char *ptr, *next;
209 std::string line; 236
210 size_t indentation = 0; 237 State state;
211 internal::parser::State state; 238
212 239 int ln;
213 int ln = 1; 240 for (ln = 0, ptr = data; ptr; ln++, ptr = next) {
214 for (; std::getline(stream, line, '\n'); ln++) { 241 next = strchr(ptr, '\n');
215 if (line.empty()) 242
243 const size_t len = (next) ? (next - ptr) : strlen(ptr);
244 if (!len)
216 continue; // Ignore empty lines 245 continue; // Ignore empty lines
217 246
218 indentation = internal::parser::GetIndentation(line); 247 char *line = (char *)malloc(len + 1);
219 248 if (!line)
220 internal::util::TrimLeft(line, "\t"); 249 return 0;
221 internal::util::TrimRight(line, "\n\r"); 250
222 251 memcpy(line, ptr, len);
223 if (line.empty() || line.front() == '#') 252 line[len] = '\0';
224 continue; // Ignore empty lines and comments 253
225 254 size_t indentation = GetIndentation(line);
226 if (!internal::parser::HandleIndentation(indentation, players, state)) { 255
227 std::cerr << "animone: indentation: failed on line " << ln << std::endl; 256 animone_internal_util_TrimLeft(line, "\t");
228 return false; 257 animone_internal_util_TrimRight(line, "\n\r");
229 } 258
230 259 if (!*line || *line == '#') {
231 if (!internal::parser::HandleState(line, players, state)) { 260 free(line);
232 std::cerr << "animone: state: failed on line " << ln << std::endl; 261 continue;
233 return false; 262 }
263
264 if (!HandleIndentation(indentation, players, &state)) {
265 fprintf(stderr, "animone: indentation: failed on line %d\n", ln);
266 free(line);
267 return 0;
268 }
269
270 if (!HandleState(line, players, &state)) {
271 fprintf(stderr, "animone: state: failed on line %d\n", ln);
272 free(line);
273 return 0;
234 } 274 }
235 } 275 }
236 276
237 return !players.empty(); 277 return !players.empty();
238 } 278 }
239 279
240 bool ParsePlayersFile(const std::string& path, std::vector<Player>& players) { 280 int animone_ParsePlayersFile(const char *path, std::vector<animone::Player>& players) {
241 std::string data; 281 char *data;
242 282
243 if (!internal::util::ReadFile(path, data)) 283 if (!animone_internal_util_ReadFile(path, &data, NULL))
244 return false; 284 return 0;
245 285
246 return ParsePlayersData(data, players); 286 int x = animone_ParsePlayersData(data, players);
247 } 287
248 288 free(data);
249 } // namespace animone 289
290 return x;
291 }