Mercurial > libanimone
diff 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 |
line wrap: on
line diff
--- a/src/player.cc Mon Feb 10 00:07:21 2025 -0500 +++ b/src/player.cc Mon Feb 10 19:17:29 2025 -0500 @@ -5,52 +5,77 @@ #include <sstream> #include <string> #include <vector> -#include <optional> #include <iostream> -namespace animone { - -namespace internal::parser { +#include <string.h> +#include <stdio.h> -struct State { - enum class Name { - ExpectPlayerName, - ExpectSection, - ExpectWindowPlatform, - ExpectExecutablePlatform, - ExpectWindow, - ExpectExecutable, - ExpectStrategy, - ExpectType, - ExpectWindowTitle, - }; +// simple crc32-hash function, derived from Hacker's Delight +// and expects a C-string as input. +// `n` should be the amount of the largest expected value of +// the hash. +static inline constexpr uint32_t crcn32b(unsigned char *message, size_t n) +{ + uint32_t crc = 0xFFFFFFFF; + size_t i = 0, j = 0; - Name state = Name::ExpectPlayerName; - WindowPlatform window_platform = WindowPlatform::Unknown; - ExecutablePlatform executable_platform = ExecutablePlatform::Unknown; -}; + for (i = 0; message[i] && i < n; i++) { + crc = crc ^ message[i]; + for (j = 0; j < 8; j++) + crc = (crc >> 1) ^ (0xEDB88320 & -(crc & 1)); + } -size_t GetIndentation(const std::string& line) { - return line.find_first_not_of('\t'); + return ~crc; } -bool HandleIndentation(const size_t current, const std::vector<Player>& players, State& state) { +typedef enum StateName { + STATENAME_EXPECTPLAYERNAME = 0, + STATENAME_EXPECTSECTION, + STATENAME_EXPECTWINDOWPLATFORM, + STATENAME_EXPECTEXECUTABLEPLATFORM, + STATENAME_EXPECTWINDOW, + STATENAME_EXPECTEXECUTABLE, + STATENAME_EXPECTSTRATEGY, + STATENAME_EXPECTTYPE, + STATENAME_EXPECTWINDOWTITLE, +} StateName; + +typedef struct State { + StateName state; + + animone::WindowPlatform window_platform; + animone::ExecutablePlatform executable_platform; +} State; + +static size_t GetIndentation(const char *line) { + return strcspn(line, "\t"); +} + +static int HandleIndentation(const size_t current, const std::vector<animone::Player>& players, State *state) { // Each state has a definitive expected indentation - const auto expected = [](const State::Name& state) -> size_t { - switch (state) { - default: - case State::Name::ExpectPlayerName: return 0; - case State::Name::ExpectSection: return 1; - case State::Name::ExpectWindowPlatform: - case State::Name::ExpectExecutablePlatform: - case State::Name::ExpectStrategy: - case State::Name::ExpectType: return 2; - case State::Name::ExpectWindow: - case State::Name::ExpectExecutable: - case State::Name::ExpectWindowTitle: return 3; - } - }(state.state); + size_t expected; + + switch (state->state) { + default: + case STATENAME_EXPECTPLAYERNAME: + expected = 0; + break; + case STATENAME_EXPECTSECTION: + expected = 1; + break; + case STATENAME_EXPECTWINDOWPLATFORM: + case STATENAME_EXPECTEXECUTABLEPLATFORM: + case STATENAME_EXPECTSTRATEGY: + case STATENAME_EXPECTTYPE: + expected = 2; + break; + case STATENAME_EXPECTWINDOW: + case STATENAME_EXPECTEXECUTABLE: + case STATENAME_EXPECTWINDOWTITLE: + expected = 3; + break; + } if (current > expected) { std::cerr << "animone: excessive indentation found" << std::endl; @@ -58,192 +83,209 @@ } if (current < expected) { - const std::optional<State::Name> st = [current, state]() -> std::optional<State::Name> { - switch (current) { - case 0: return State::Name::ExpectPlayerName; - default: - case 1: return State::Name::ExpectSection; - case 2: - switch (state.state) { - case State::Name::ExpectWindow: return State::Name::ExpectWindowPlatform; - case State::Name::ExpectExecutable: return State::Name::ExpectExecutablePlatform; - default: return std::nullopt; - } - } - }(); - if (!st.has_value()) - return false; - - switch (state.state) { - case State::Name::ExpectWindow: - if (players.back().windows.empty()) - return false; - state.state = st.value(); - break; - case State::Name::ExpectExecutable: - if (players.back().executables.empty()) - return false; - state.state = st.value(); - break; - case State::Name::ExpectStrategy: - if (players.back().strategies.empty()) - return false; - state.state = st.value(); - break; - case State::Name::ExpectType: - state.state = st.value(); - break; - case State::Name::ExpectWindowTitle: - return false; - } - } - - return true; -} - -bool HandleState(std::string& line, std::vector<Player>& players, State& state) { - switch (state.state) { - case State::Name::ExpectPlayerName: - players.push_back(Player()); - players.back().name = line; - state.state = State::Name::ExpectSection; - break; + StateName name; - case State::Name::ExpectSection: { - static const std::map<std::string, State::Name> sections = { - {"windows", State::Name::ExpectWindowPlatform}, - {"executables", State::Name::ExpectExecutablePlatform}, - {"strategies", State::Name::ExpectStrategy }, - {"type", State::Name::ExpectType }, - }; - util::TrimRight(line, ":"); - const auto it = sections.find(line); - if (it == sections.end()) - return false; - state.state = it->second; - break; - } - - case State::Name::ExpectWindowPlatform: { - static const std::map<std::string, WindowPlatform> platforms = { - {"quartz", WindowPlatform::Quartz}, - {"win32", WindowPlatform::Win32}, - {"x11", WindowPlatform::X11}, - }; - util::TrimRight(line, ":"); - const auto it = platforms.find(line); - if (it == platforms.end()) - return false; - state.state = State::Name::ExpectWindow; - state.window_platform = it->second; - break; - } - - case State::Name::ExpectExecutablePlatform: { - static const std::map<std::string, ExecutablePlatform> platforms = { - {"posix", ExecutablePlatform::Posix}, - {"win32", ExecutablePlatform::Win32}, - {"macosx", ExecutablePlatform::Xnu}, - }; - util::TrimRight(line, ":"); - const auto it = platforms.find(line); - if (it == platforms.end()) - return false; - state.state = State::Name::ExpectExecutable; - state.executable_platform = it->second; - break; - } - - case State::Name::ExpectWindow: players.back().windows[state.window_platform].push_back(line); break; - - case State::Name::ExpectExecutable: players.back().executables[state.executable_platform].push_back(line); break; - - case State::Name::ExpectStrategy: { - static const std::map<std::string, Strategy> strategies = { - {"window_title", Strategy::WindowTitle }, - {"open_files", Strategy::OpenFiles }, - {"ui_automation", Strategy::UiAutomation}, - }; - util::TrimRight(line, ":"); - const auto it = strategies.find(line); - if (it == strategies.end()) - return false; - const auto strategy = it->second; - players.back().strategies.push_back(strategy); - switch (strategy) { - case Strategy::WindowTitle: state.state = State::Name::ExpectWindowTitle; break; + switch (current) { + case 0: name = STATENAME_EXPECTPLAYERNAME; break; + default: + case 1: name = STATENAME_EXPECTSECTION; break; + case 2: + switch (state->state) { + case STATENAME_EXPECTWINDOW: name = STATENAME_EXPECTWINDOWPLATFORM; break; + case STATENAME_EXPECTEXECUTABLE: name = STATENAME_EXPECTEXECUTABLEPLATFORM; break; + default: return 0; } break; } - case State::Name::ExpectType: { - static const std::map<std::string, PlayerType> types = { - {"default", PlayerType::Default }, - {"web_browser", PlayerType::WebBrowser}, - }; - const auto it = types.find(line); - if (it == types.end()) - return false; - players.back().type = it->second; + switch (state->state) { + case STATENAME_EXPECTWINDOW: + if (players.back().windows.empty()) + return 0; + state->state = name; + break; + case STATENAME_EXPECTEXECUTABLE: + if (players.back().executables.empty()) + return 0; + state->state = name; + break; + case STATENAME_EXPECTSTRATEGY: + if (players.back().strategies.empty()) + return 0; + state->state = name; + break; + case STATENAME_EXPECTTYPE: + state->state = name; + break; + case STATENAME_EXPECTWINDOWTITLE: + return 0; + default: + break; // ??? + } + } + + return 1; +} + +bool HandleState(char *line, std::vector<animone::Player>& players, State *state) { + switch (state->state) { + case STATENAME_EXPECTPLAYERNAME: + players.push_back(animone::Player()); + players.back().name = line; + state->state = STATENAME_EXPECTSECTION; + break; + + case STATENAME_EXPECTSECTION: + animone_internal_util_TrimRight(line, ":"); + + switch (crcn32b((unsigned char *)line, 11)) { // max: "executables" + case 0xe3e7859b: state->state = STATENAME_EXPECTWINDOWPLATFORM; break; // "windows" + case 0x6cdf7147: state->state = STATENAME_EXPECTEXECUTABLEPLATFORM; break; // "executables" + case 0x611f2213: state->state = STATENAME_EXPECTSTRATEGY; break; // "strategies" + case 0x8cde5729: state->state = STATENAME_EXPECTTYPE; break; // "type" + default: return 0; + } + + break; + + case STATENAME_EXPECTWINDOWPLATFORM: + animone_internal_util_TrimRight(line, ":"); + + switch (crcn32b((unsigned char *)line, 6)) { // max: "quartz" + case 0x6fe218f0: state->window_platform = animone::WindowPlatform::Quartz; break; // "quartz" + case 0xb50ba1d1: state->window_platform = animone::WindowPlatform::Win32; break; // "win32" + case 0x3220e772: state->window_platform = animone::WindowPlatform::X11; break; // "x11" + default: return 0; + } + + state->state = STATENAME_EXPECTWINDOW; + break; + + case STATENAME_EXPECTEXECUTABLEPLATFORM: + animone_internal_util_TrimRight(line, ":"); + + switch (crcn32b((unsigned char *)line, 6)) { // max: "macosx" + case 0xe0e30f6e: state->executable_platform = animone::ExecutablePlatform::Posix; break; // "posix" + case 0xb50ba1d1: state->executable_platform = animone::ExecutablePlatform::Win32; break; // "win32" + case 0x912ee411: state->executable_platform = animone::ExecutablePlatform::Xnu; break; // "macosx" + default: return 0; + } + + state->state = STATENAME_EXPECTEXECUTABLE; + break; + + case STATENAME_EXPECTWINDOW: + players.back().windows[state->window_platform].push_back(line); + break; + + case STATENAME_EXPECTEXECUTABLE: + players.back().executables[state->executable_platform].push_back(line); + break; + + case STATENAME_EXPECTSTRATEGY: { + /* XXX PORT: These should be bit flags instead. */ + animone_internal_util_TrimRight(line, ":"); + + animone::Strategy strategy; + + switch (crcn32b((unsigned char *)line, 13)) { // max: "ui_automation" + case 0xfef78b24: strategy = animone::Strategy::WindowTitle; break; // "window_title" + case 0x4d88605f: strategy = animone::Strategy::OpenFiles; break; // "open_files" + case 0xdceb02f6: strategy = animone::Strategy::UiAutomation; break; // "ui_automation" + default: return 0; + } + + players.back().strategies.push_back(strategy); + + switch (strategy) { + case animone::Strategy::WindowTitle: + state->state = STATENAME_EXPECTWINDOWTITLE; + break; + default: break; } - - case State::Name::ExpectWindowTitle: - players.back().window_title_format = line; - state.state = State::Name::ExpectStrategy; - break; + break; } - return true; + case STATENAME_EXPECTTYPE: { + switch (crcn32b((unsigned char *)line, 13)) { // max: "ui_automation" + case 0xe35e00df: players.back().type = animone::PlayerType::Default; break; // "default" + case 0x32bca1a4: players.back().type = animone::PlayerType::WebBrowser; break; // "web_browser" + default: return 0; + } + + break; + } + + case STATENAME_EXPECTWINDOWTITLE: + players.back().window_title_format = line; + state->state = STATENAME_EXPECTSTRATEGY; + break; + } + + return 1; } -} // namespace internal::parser - //////////////////////////////////////////////////////////////////////////////// -bool ParsePlayersData(const std::string& data, std::vector<Player>& players) { - if (data.empty()) - return false; +int animone_ParsePlayersData(const char *data, std::vector<animone::Player>& players) { + if (!data || !*data) + return 0; + + const char *ptr, *next; - std::istringstream stream(data); - std::string line; - size_t indentation = 0; - internal::parser::State state; + State state; - int ln = 1; - for (; std::getline(stream, line, '\n'); ln++) { - if (line.empty()) + int ln; + for (ln = 0, ptr = data; ptr; ln++, ptr = next) { + next = strchr(ptr, '\n'); + + const size_t len = (next) ? (next - ptr) : strlen(ptr); + if (!len) continue; // Ignore empty lines - indentation = internal::parser::GetIndentation(line); + char *line = (char *)malloc(len + 1); + if (!line) + return 0; - internal::util::TrimLeft(line, "\t"); - internal::util::TrimRight(line, "\n\r"); + memcpy(line, ptr, len); + line[len] = '\0'; - if (line.empty() || line.front() == '#') - continue; // Ignore empty lines and comments + size_t indentation = GetIndentation(line); - if (!internal::parser::HandleIndentation(indentation, players, state)) { - std::cerr << "animone: indentation: failed on line " << ln << std::endl; - return false; + animone_internal_util_TrimLeft(line, "\t"); + animone_internal_util_TrimRight(line, "\n\r"); + + if (!*line || *line == '#') { + free(line); + continue; } - if (!internal::parser::HandleState(line, players, state)) { - std::cerr << "animone: state: failed on line " << ln << std::endl; - return false; + if (!HandleIndentation(indentation, players, &state)) { + fprintf(stderr, "animone: indentation: failed on line %d\n", ln); + free(line); + return 0; + } + + if (!HandleState(line, players, &state)) { + fprintf(stderr, "animone: state: failed on line %d\n", ln); + free(line); + return 0; } } return !players.empty(); } -bool ParsePlayersFile(const std::string& path, std::vector<Player>& players) { - std::string data; +int animone_ParsePlayersFile(const char *path, std::vector<animone::Player>& players) { + char *data; + + if (!animone_internal_util_ReadFile(path, &data, NULL)) + return 0; - if (!internal::util::ReadFile(path, data)) - return false; + int x = animone_ParsePlayersData(data, players); - return ParsePlayersData(data, players); + free(data); + + return x; } - -} // namespace animone