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