view dep/animia/src/player.cc @ 187:9613d72b097e

*: multiple performance improvements like marking `static const` when it makes sense... date: change old stupid heap-based method to a structure which should make copying the thing actually make a copy. also many performance-based changes, like removing the std::tie dependency and forward-declaring nlohmann json *: replace every instance of QString::fromUtf8 to Strings::ToQString. the main difference is that our function will always convert exactly what is in the string, while some other times it would only convert up to the nearest NUL byte
author Paper <mrpapersonic@gmail.com>
date Wed, 06 Dec 2023 13:43:54 -0500
parents cdf79282d647
children
line wrap: on
line source

#include "animia/player.h"
#include "animia/util.h"

#include <map>
#include <sstream>
#include <string>
#include <vector>

namespace animia {

namespace internal::parser {

enum class State {
	ExpectPlayerName,
	ExpectSection,
	ExpectWindow,
	ExpectExecutable,
	ExpectStrategy,
	ExpectType,
	ExpectWindowTitle,
};

size_t GetIndentation(const std::string& line) {
	return line.find_first_not_of('\t');
}

bool HandleIndentation(const size_t current, const std::vector<Player>& players, State& state) {
	// Each state has a definitive expected indentation
	const auto expected = [&state]() -> size_t {
		switch (state) {
			default:
			case State::ExpectPlayerName: return 0;
			case State::ExpectSection: return 1;
			case State::ExpectWindow:
			case State::ExpectExecutable:
			case State::ExpectStrategy:
			case State::ExpectType: return 2;
			case State::ExpectWindowTitle: return 3;
		}
	}();

	if (current > expected)
		return false; // Disallow excessive indentation

	if (current < expected) {
		auto fix_state = [&]() { state = !current ? State::ExpectPlayerName : State::ExpectSection; };
		switch (state) {
			case State::ExpectWindow:
				if (players.back().windows.empty())
					return false;
				fix_state();
				break;
			case State::ExpectExecutable:
				if (players.back().executables.empty())
					return false;
				fix_state();
				break;
			case State::ExpectStrategy:
				if (players.back().strategies.empty())
					return false;
				fix_state();
				break;
			case State::ExpectType: fix_state(); break;
			case State::ExpectWindowTitle: return false;
		}
	}

	return true;
}

bool HandleState(std::string& line, std::vector<Player>& players, State& state) {
	switch (state) {
		case State::ExpectPlayerName:
			players.push_back(Player());
			players.back().name = line;
			state = State::ExpectSection;
			break;

		case State::ExpectSection: {
			static const std::map<std::string, State> sections = {
			    {"windows",     State::ExpectWindow    },
			    {"executables", State::ExpectExecutable},
			    {"strategies",  State::ExpectStrategy  },
			    {"type",        State::ExpectType      },
			};
			util::TrimRight(line, ":");
			const auto it = sections.find(line);
			if (it == sections.end())
				return false;
			state = it->second;
			break;
		}

		case State::ExpectWindow: players.back().windows.push_back(line); break;

		case State::ExpectExecutable: players.back().executables.push_back(line); break;

		case State::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::ExpectWindowTitle; break;
			}
			break;
		}

		case State::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;
			break;
		}

		case State::ExpectWindowTitle:
			players.back().window_title_format = line;
			state = State::ExpectStrategy;
			break;
	}

	return true;
}

} // namespace internal::parser

////////////////////////////////////////////////////////////////////////////////

bool ParsePlayersData(const std::string& data, std::vector<Player>& players) {
	if (data.empty())
		return false;

	std::istringstream stream(data);
	std::string line;
	size_t indentation = 0;
	auto state = internal::parser::State::ExpectPlayerName;

	while (std::getline(stream, line, '\n')) {
		if (line.empty())
			continue; // Ignore empty lines

		indentation = internal::parser::GetIndentation(line);

		internal::util::TrimLeft(line, "\t");
		internal::util::TrimRight(line, "\n\r");

		if (line.empty() || line.front() == '#')
			continue; // Ignore empty lines and comments

		if (!internal::parser::HandleIndentation(indentation, players, state))
			return false;

		if (!internal::parser::HandleState(line, players, state))
			return false;
	}

	return !players.empty();
}

bool ParsePlayersFile(const std::string& path, std::vector<Player>& players) {
	std::string data;

	if (!internal::util::ReadFile(path, data))
		return false;

	return ParsePlayersData(data, players);
}

} // namespace animia