Mercurial > libanimone
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 } |