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