view 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 source

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

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

#include <iostream>

#include <string.h>
#include <stdio.h>

// 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;

	for (i = 0; message[i] && i < n; i++) {
		crc = crc ^ message[i];
		for (j = 0; j < 8; j++)
			crc = (crc >> 1) ^ (0xEDB88320 & -(crc & 1));
	}

	return ~crc;
}

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
	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;
		return false; // Disallow excessive indentation
	}

	if (current < expected) {
		StateName name;

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

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

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

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

int animone_ParsePlayersData(const char *data, std::vector<animone::Player>& players) {
	if (!data || !*data)
		return 0;

	const char *ptr, *next;

	State state;

	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

		char *line = (char *)malloc(len + 1);
		if (!line)
			return 0;

		memcpy(line, ptr, len);
		line[len] = '\0';

		size_t indentation = GetIndentation(line);

		animone_internal_util_TrimLeft(line, "\t");
		animone_internal_util_TrimRight(line, "\n\r");

		if (!*line || *line == '#') {
			free(line);
			continue;
		}

		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();
}

int animone_ParsePlayersFile(const char *path, std::vector<animone::Player>& players) {
	char *data;

	if (!animone_internal_util_ReadFile(path, &data, NULL))
		return 0;

	int x = animone_ParsePlayersData(data, players);

	free(data);

	return x;
}