pokeplatinum/tools/datagen/datagen.h

242 lines
7.3 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.
*/
// clang-format off
#include <algorithm>
#include <cstdint>
#include <cstdlib>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <sstream>
#include <stdexcept>
#include <string>
#include <vector>
#include <rapidjson/document.h>
#include <rapidjson/error/en.h>
#include <narc/narc.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;
}
// Pack a NARC to an output path from a VFS context.
static inline void PackNarc(vfs_pack_ctx *ctx, fs::path path)
{
narc *narc = narc_pack(ctx);
std::ofstream ofs(path);
ofs.write(reinterpret_cast<char *>(narc), narc->size);
free(narc);
}
// Pack a single-file NARC of binary elements to an output path.
template <typename T>
static inline void PackSingleFileNarc(std::vector<T> &elems, fs::path path)
{
if (elems.empty()) {
return;
}
vfs_pack_ctx *vfs = narc_pack_start();
narc_pack_file_copy(vfs, reinterpret_cast<unsigned char *>(elems.data()), sizeof(elems[0]) * elems.size());
PackNarc(vfs, 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;
}
}
#endif // POKEPLATINUM_DATAGEN_H