pokeplatinum/tools/datagen/datagen.h

321 lines
10 KiB
C

#ifndef POKEPLATINUM_DATAGEN_H
#define POKEPLATINUM_DATAGEN_H
/*
* This is a shareable header of utility functions which are of use to data generation
* and packing programs.
*/
#include <algorithm>
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <ostream>
#include <sstream>
#include <stdexcept>
#include <string>
#include <vector>
// clang-format off
#include <nitroarc.h>
#include <rapidjson/document.h>
#include <rapidjson/error/en.h>
#include <rapidjson/filewritestream.h>
#include <rapidjson/writer.h>
// clang-format on
namespace fs = std::filesystem;
// This attribute is useful when working with structures that have an element
// which must be word-aligned. For an example (and reasoning), see
// `SpeciesEvolutionList` in `datagen_species.cpp`.
#define ALIGN_4 __attribute__((aligned(4)))
typedef uint8_t u8;
typedef uint16_t u16;
typedef uint32_t u32;
typedef int8_t s8;
typedef int16_t s16;
typedef int32_t s32;
typedef bool BOOL;
#define TRUE true
#define FALSE false
// A lookup-table entry, mirroring the definition of structures generated by `metang`.
struct LookupEntry {
const long value;
const char *def;
};
// Search through a lookup-table for a string value.
static inline long Search(const LookupEntry *lookupTable, int low, int high, const char *val)
{
while (low <= high) {
int mid = low + (high - low) / 2;
if (strcmp(val, lookupTable[mid].def) == 0) {
return lookupTable[mid].value;
}
const char *val_p = val;
const char *def_p = lookupTable[mid].def;
while (*val_p && *def_p && *val_p == *def_p) {
val_p++;
def_p++;
}
if (*val_p > *def_p) {
low = mid + 1;
} else {
high = mid - 1;
}
}
return -1;
}
// Lookup a constant from a lookup-table. If the value is not found, then an
// `invalid_argument` exception will be thrown. A C-string is taken for the
// lookup-value for compatibility with rapidjson, which only uses C-strings.
static inline long Lookup(const LookupEntry *lookupTable, int low, int high, const char *val, const std::string &valDesc)
{
long result = Search(lookupTable, low, high, val);
if (result < 0) {
std::stringstream buf;
buf << "no match found for " << val << " as " << valDesc;
throw std::invalid_argument(buf.str());
}
return result;
}
// A C++-string wrapper to lookup a constant from a lookup table.
static inline long Lookup(const LookupEntry *lookupTable, int low, int high, const std::string &val, const std::string &valDesc)
{
return Lookup(lookupTable, low, high, val.c_str(), valDesc);
}
// This macro is a standardized bit of short-hand to lookup a constant value
// of a particular type. e.g., to lookup an Ability constant string's value, you
// would invoke `LookupConst(abilityString, Ability)`; this would expand to:
// `Lookup((LookupEntry *)lookup__Ability, 0, num__Ability, abilityString, "Ability")`
#define LookupConst(val, T) Lookup(reinterpret_cast<const LookupEntry *>(lookup__##T), 0, lengthof__##T, val, #T)
// Align a value to the next 4-byte word-boundary.
static inline long AlignToWord(long val)
{
return val + (-val & 3);
}
// Tokenize a string by a given delimiter into a vector of components.
static inline std::vector<std::string> Tokenize(const std::string &s, const char delim = ' ')
{
std::vector<std::string> tokens;
std::size_t start = s.find_first_not_of(delim, 0);
std::size_t end = 0;
while (start != std::string::npos) {
end = s.find_first_of(delim, start);
tokens.emplace_back(s.substr(start, end - start));
start = s.find_first_not_of(delim, end);
}
return tokens;
}
// Wrapper class around nitroarc's packing API
class NarcBuilder {
nitroarc_packer_t packer = { 0 };
static void* libc_malloc(void *ctx, unsigned items, unsigned size) {
(void)ctx;
return malloc(items * size);
}
static void libc_free(void *ctx, void *ptr, unsigned items, unsigned size) {
(void)ctx;
(void)items;
(void)size;
free(ptr);
}
static void* libc_realloc(void *ctx, void *ptr, unsigned items, unsigned size) {
(void)ctx;
return realloc(ptr, items * size);
}
public:
NarcBuilder(std::size_t num_files, bool named = false, bool stripped = false) {
packer.malloc = libc_malloc;
packer.realloc = libc_realloc;
packer.free = libc_free;
if (int errc = nitroarc_pinit(&this->packer, num_files, named, stripped); errc) {
throw std::runtime_error(nitroarc_errs(errc));
}
}
struct Span {
std::byte *ptr;
u32 size;
};
void append(std::byte *ptr, u32 size, char *name = nullptr) {
if (int errc = nitroarc_ppack(&this->packer, ptr, size, name); errc) {
throw std::runtime_error(nitroarc_errs(errc));
}
}
void append(Span &span, char *name = nullptr) { append(span.ptr, span.size, name); }
Span build() {
void *result = nullptr;
u32 size = 0;
if (int errc = nitroarc_pseal(&this->packer, &result, &size); errc) {
throw std::runtime_error(nitroarc_errs(errc));
}
return Span { reinterpret_cast<std::byte *>(result), size };
}
void write(fs::path path) {
std::ofstream ofs(path);
Span narc = this->build();
ofs.write(reinterpret_cast<char *>(narc.ptr), narc.size);
free(narc.ptr);
}
template <typename T>
static void write(std::vector<T> &elems, fs::path path, char *name = nullptr) {
if (elems.empty()) return;
NarcBuilder b { 1 };
b.append(reinterpret_cast<std::byte *>(elems.data()), sizeof(elems[0]) * elems.size(), name);
b.write(path);
}
};
// Read a whole file into a C++-string.
static inline std::string ReadWholeFile(std::ifstream &ifs)
{
ifs.seekg(0, std::ios::end);
size_t size = ifs.tellg();
std::string contents(size, ' ');
ifs.seekg(0);
ifs.read(&contents[0], size);
return contents;
}
// Read a whole file into a C++-string.
// This routine is a potential exit-point if the file cannot be loaded for reading.
static inline std::string ReadWholeFile(fs::path &fname)
{
std::ifstream ifs(fname, std::ios::in);
if (ifs.fail()) {
std::cerr << "could not open file " << fs::relative(fname).generic_string() << std::endl;
std::exit(EXIT_FAILURE);
}
return ReadWholeFile(ifs);
}
// Read a file's lines into a vector of C++ strings.
static inline std::vector<std::string> ReadFileLines(fs::path &fname)
{
std::ifstream ifs(fname);
std::vector<std::string> lines;
std::string line;
while (std::getline(ifs, line)) {
lines.emplace_back(line);
}
return lines;
}
// Read a registry-type environment variable into a vector of C++ strings.
static inline std::vector<std::string> ReadRegistryEnvVar(const char *var)
{
const char *val_p = NULL;
if ((val_p = std::getenv(var)) == NULL) {
std::cerr << "Missing definition for registry environment variable " << var << std::endl;
std::exit(EXIT_FAILURE);
}
std::string val = val_p;
return Tokenize(val, ';');
}
struct Slice {
long begin;
long length;
};
static inline void ReportJsonError(rapidjson::ParseResult ok, std::string &json, fs::path &sourcepath)
{
std::vector<Slice> linecoords { Slice { 0, 0 } };
for (int i = 0; i < json.length(); i++) {
if (json.at(i) == '\n') {
linecoords.back().length = i - linecoords.back().begin;
linecoords.emplace_back(Slice { i + 1, 0 });
}
}
linecoords.back().length = json.length() - linecoords.back().begin;
auto line = std::find_if(linecoords.begin(), linecoords.end(), [&ok](Slice slice) {
return slice.length + slice.begin >= ok.Offset();
});
auto lineidx = std::distance(linecoords.begin(), line);
auto linenum = lineidx + 1;
auto colnum = ok.Offset() - line->begin + 1;
std::cerr << "\033[1;37m" << fs::relative(sourcepath).generic_string() << ":" << linenum << ":" << colnum << ": ";
std::cerr << "\033[1;31merror: ";
std::cerr << "\033[1;37m" << rapidjson::GetParseError_En(ok.Code());
std::cerr << "\033[0m" << std::endl;
if (lineidx > 0) {
auto &prev = linecoords.at(lineidx - 1);
std::cerr << std::setw(5) << linenum - 1 << " | " << json.substr(prev.begin, prev.length) << std::endl;
}
std::cerr << std::setw(5) << linenum << " | " << "\033[0;31m" << json.substr(line->begin, line->length) << "\033[0m" << std::endl;
if (lineidx < linecoords.size() - 1) {
auto &next = linecoords.at(lineidx + 1);
std::cerr << std::setw(5) << linenum + 1 << " | " << json.substr(next.begin, next.length) << std::endl;
}
}
static inline void CopyMessage(const rapidjson::Value &member, rapidjson::Value &outMessage, rapidjson::MemoryPoolAllocator<> &allocator)
{
if (member.HasMember("en_US")) {
if (member["en_US"].IsArray()) {
rapidjson::Value strings(rapidjson::kArrayType);
for (const auto &line : member["en_US"].GetArray()) {
std::string str = line.GetString();
rapidjson::Value string(rapidjson::kStringType);
string.SetString(str.c_str(), static_cast<rapidjson::SizeType>(str.length()), allocator);
strings.PushBack(string, allocator);
}
outMessage.AddMember("en_US", strings, allocator);
} else {
std::string str = member["en_US"].GetString();
rapidjson::Value string(rapidjson::kStringType);
string.SetString(str.c_str(), static_cast<rapidjson::SizeType>(str.length()), allocator);
outMessage.AddMember("en_US", string, allocator);
}
} else if (member.HasMember("garbage")) {
outMessage.AddMember("garbage", member["garbage"].GetInt(), allocator);
}
}
#endif // POKEPLATINUM_DATAGEN_H