| 258 | 1 #include "animone/player.h" | 
|  | 2 #include "animone/util.h" | 
|  | 3 | 
|  | 4 #include <map> | 
|  | 5 #include <sstream> | 
|  | 6 #include <string> | 
|  | 7 #include <vector> | 
|  | 8 | 
|  | 9 namespace animone { | 
|  | 10 | 
|  | 11 namespace internal::parser { | 
|  | 12 | 
|  | 13 enum class State { | 
|  | 14 	ExpectPlayerName, | 
|  | 15 	ExpectSection, | 
|  | 16 	ExpectWindow, | 
|  | 17 	ExpectExecutable, | 
|  | 18 	ExpectStrategy, | 
|  | 19 	ExpectType, | 
|  | 20 	ExpectWindowTitle, | 
|  | 21 }; | 
|  | 22 | 
|  | 23 size_t GetIndentation(const std::string& line) { | 
|  | 24 	return line.find_first_not_of('\t'); | 
|  | 25 } | 
|  | 26 | 
|  | 27 bool HandleIndentation(const size_t current, const std::vector<Player>& players, State& state) { | 
|  | 28 	// Each state has a definitive expected indentation | 
|  | 29 	const auto expected = [&state]() -> size_t { | 
|  | 30 		switch (state) { | 
|  | 31 			default: | 
|  | 32 			case State::ExpectPlayerName: return 0; | 
|  | 33 			case State::ExpectSection: return 1; | 
|  | 34 			case State::ExpectWindow: | 
|  | 35 			case State::ExpectExecutable: | 
|  | 36 			case State::ExpectStrategy: | 
|  | 37 			case State::ExpectType: return 2; | 
|  | 38 			case State::ExpectWindowTitle: return 3; | 
|  | 39 		} | 
|  | 40 	}(); | 
|  | 41 | 
|  | 42 	if (current > expected) | 
|  | 43 		return false; // Disallow excessive indentation | 
|  | 44 | 
|  | 45 	if (current < expected) { | 
|  | 46 		auto fix_state = [&]() { state = !current ? State::ExpectPlayerName : State::ExpectSection; }; | 
|  | 47 		switch (state) { | 
|  | 48 			case State::ExpectWindow: | 
|  | 49 				if (players.back().windows.empty()) | 
|  | 50 					return false; | 
|  | 51 				fix_state(); | 
|  | 52 				break; | 
|  | 53 			case State::ExpectExecutable: | 
|  | 54 				if (players.back().executables.empty()) | 
|  | 55 					return false; | 
|  | 56 				fix_state(); | 
|  | 57 				break; | 
|  | 58 			case State::ExpectStrategy: | 
|  | 59 				if (players.back().strategies.empty()) | 
|  | 60 					return false; | 
|  | 61 				fix_state(); | 
|  | 62 				break; | 
|  | 63 			case State::ExpectType: fix_state(); break; | 
|  | 64 			case State::ExpectWindowTitle: return false; | 
|  | 65 		} | 
|  | 66 	} | 
|  | 67 | 
|  | 68 	return true; | 
|  | 69 } | 
|  | 70 | 
|  | 71 bool HandleState(std::string& line, std::vector<Player>& players, State& state) { | 
|  | 72 	switch (state) { | 
|  | 73 		case State::ExpectPlayerName: | 
|  | 74 			players.push_back(Player()); | 
|  | 75 			players.back().name = line; | 
|  | 76 			state = State::ExpectSection; | 
|  | 77 			break; | 
|  | 78 | 
|  | 79 		case State::ExpectSection: { | 
|  | 80 			static const std::map<std::string, State> sections = { | 
|  | 81 			    {"windows",     State::ExpectWindow    }, | 
|  | 82 			    {"executables", State::ExpectExecutable}, | 
|  | 83 			    {"strategies",  State::ExpectStrategy  }, | 
|  | 84 			    {"type",        State::ExpectType      }, | 
|  | 85 			}; | 
|  | 86 			util::TrimRight(line, ":"); | 
|  | 87 			const auto it = sections.find(line); | 
|  | 88 			if (it == sections.end()) | 
|  | 89 				return false; | 
|  | 90 			state = it->second; | 
|  | 91 			break; | 
|  | 92 		} | 
|  | 93 | 
|  | 94 		case State::ExpectWindow: players.back().windows.push_back(line); break; | 
|  | 95 | 
|  | 96 		case State::ExpectExecutable: players.back().executables.push_back(line); break; | 
|  | 97 | 
|  | 98 		case State::ExpectStrategy: { | 
|  | 99 			static const std::map<std::string, Strategy> strategies = { | 
|  | 100 			    {"window_title",  Strategy::WindowTitle }, | 
|  | 101 			    {"open_files",    Strategy::OpenFiles   }, | 
|  | 102 			    {"ui_automation", Strategy::UiAutomation}, | 
|  | 103 			}; | 
|  | 104 			util::TrimRight(line, ":"); | 
|  | 105 			const auto it = strategies.find(line); | 
|  | 106 			if (it == strategies.end()) | 
|  | 107 				return false; | 
|  | 108 			const auto strategy = it->second; | 
|  | 109 			players.back().strategies.push_back(strategy); | 
|  | 110 			switch (strategy) { | 
|  | 111 				case Strategy::WindowTitle: state = State::ExpectWindowTitle; break; | 
|  | 112 			} | 
|  | 113 			break; | 
|  | 114 		} | 
|  | 115 | 
|  | 116 		case State::ExpectType: { | 
|  | 117 			static const std::map<std::string, PlayerType> types = { | 
|  | 118 			    {"default",     PlayerType::Default   }, | 
|  | 119 			    {"web_browser", PlayerType::WebBrowser}, | 
|  | 120 			}; | 
|  | 121 			const auto it = types.find(line); | 
|  | 122 			if (it == types.end()) | 
|  | 123 				return false; | 
|  | 124 			players.back().type = it->second; | 
|  | 125 			break; | 
|  | 126 		} | 
|  | 127 | 
|  | 128 		case State::ExpectWindowTitle: | 
|  | 129 			players.back().window_title_format = line; | 
|  | 130 			state = State::ExpectStrategy; | 
|  | 131 			break; | 
|  | 132 	} | 
|  | 133 | 
|  | 134 	return true; | 
|  | 135 } | 
|  | 136 | 
|  | 137 } // namespace internal::parser | 
|  | 138 | 
|  | 139 //////////////////////////////////////////////////////////////////////////////// | 
|  | 140 | 
|  | 141 bool ParsePlayersData(const std::string& data, std::vector<Player>& players) { | 
|  | 142 	if (data.empty()) | 
|  | 143 		return false; | 
|  | 144 | 
|  | 145 	std::istringstream stream(data); | 
|  | 146 	std::string line; | 
|  | 147 	size_t indentation = 0; | 
|  | 148 	auto state = internal::parser::State::ExpectPlayerName; | 
|  | 149 | 
|  | 150 	while (std::getline(stream, line, '\n')) { | 
|  | 151 		if (line.empty()) | 
|  | 152 			continue; // Ignore empty lines | 
|  | 153 | 
|  | 154 		indentation = internal::parser::GetIndentation(line); | 
|  | 155 | 
|  | 156 		internal::util::TrimLeft(line, "\t"); | 
|  | 157 		internal::util::TrimRight(line, "\n\r"); | 
|  | 158 | 
|  | 159 		if (line.empty() || line.front() == '#') | 
|  | 160 			continue; // Ignore empty lines and comments | 
|  | 161 | 
|  | 162 		if (!internal::parser::HandleIndentation(indentation, players, state)) | 
|  | 163 			return false; | 
|  | 164 | 
|  | 165 		if (!internal::parser::HandleState(line, players, state)) | 
|  | 166 			return false; | 
|  | 167 	} | 
|  | 168 | 
|  | 169 	return !players.empty(); | 
|  | 170 } | 
|  | 171 | 
|  | 172 bool ParsePlayersFile(const std::string& path, std::vector<Player>& players) { | 
|  | 173 	std::string data; | 
|  | 174 | 
|  | 175 	if (!internal::util::ReadFile(path, data)) | 
|  | 176 		return false; | 
|  | 177 | 
|  | 178 	return ParsePlayersData(data, players); | 
|  | 179 } | 
|  | 180 | 
|  | 181 } // namespace animone |