sovereignx/tools/trainerproc/main.c

2026 lines
58 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* trainerprpc
* Parses teams in the competitive format (as used by Showdown) and
* converts them into the internal team format. */
/* To add a new member to 'TrainerMon':
* 1. Add the same member to 'Pokemon'.
* 2. Parse that member in 'parse_trainer', probably in the 'parse_attribute' loop.
* 3. Format that member in 'fprint_trainers'. */
#include <assert.h>
#include <limits.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#define MAX_TRAINER_AI_FLAGS 32
#define MAX_TRAINER_ITEMS 4
#define PARTY_SIZE 6
#define MAX_MON_MOVES 4
struct String
{
const unsigned char *string;
int string_n;
};
struct Stats
{
int hp, attack, defense, special_attack, special_defense, speed;
};
enum Gender
{
GENDER_ANY,
GENDER_MALE,
GENDER_FEMALE,
};
// TODO: Support Hidden Power.
struct Pokemon
{
struct String nickname;
struct String species;
enum Gender gender;
struct String item;
int header_line;
struct Stats evs;
int evs_line;
struct Stats ivs;
int ivs_line;
struct String ability;
int ability_line;
int level;
int level_line;
struct String ball;
int ball_line;
int friendship;
int friendship_line;
struct String nature;
int nature_line;
bool shiny;
int shiny_line;
int dynamax_level;
int dynamax_level_line;
bool gigantamax_factor;
bool gigantamax_factor_line;
struct String tera_type;
int tera_type_line;
struct String moves[MAX_MON_MOVES];
int moves_n;
int move1_line;
};
struct Trainer
{
struct String id;
int id_line;
struct String ai_flags[MAX_TRAINER_AI_FLAGS];
int ai_flags_n;
int ai_flags_line;
struct String items[MAX_TRAINER_ITEMS];
int items_n;
int items_line;
struct String class;
int class_line;
struct String encounter_music;
int encounter_music_line;
enum Gender gender;
int gender_line;
struct String pic;
int pic_line;
struct String name;
int name_line;
bool double_battle;
int double_battle_line;
struct Pokemon pokemon[PARTY_SIZE];
int pokemon_n;
struct String mugshot;
int mugshot_line;
struct String starting_status;
int starting_status_line;
};
static bool is_empty_string(struct String s)
{
return s.string_n == 0;
}
static bool is_literal_string(struct String s1, const char *s2)
{
for (int i = 0;; i++)
{
if (i == s1.string_n && s2[i] == '\0')
return true;
else if (i == s1.string_n || s2[i] == '\0')
return false;
else if (s1.string[i] != s2[i])
return false;
}
}
static bool starts_with(struct String s, const char *prefix)
{
int n = strlen(prefix);
return strncmp((const char *)s.string, prefix, n) == 0;
}
static bool ends_with(struct String s, const char *suffix)
{
int n = strlen(suffix);
if (n > s.string_n)
{
return false;
}
else
{
for (int i = 0; i < n; i++)
{
if (s.string[s.string_n - i - 1] != suffix[n - i - 1])
return false;
}
return true;
}
}
static struct String literal_string(const char *s)
{
return (struct String) {
.string = (const unsigned char *)s,
.string_n = strlen(s),
};
}
struct Source
{
const char *path;
const unsigned char *buffer;
int buffer_n;
};
struct SourceLocation
{
int line;
int column;
};
struct Token
{
const struct Source *source;
struct SourceLocation location;
int begin;
int end;
};
static bool is_empty_token(const struct Token *t)
{
return t->begin == t->end;
}
// TODO: Make case-insensitive?
static bool is_literal_token(const struct Token *t, const char *s)
{
int i = t->begin;
for (;;)
{
if (*s == '\0' && i == t->end)
return true;
else if (*s == '\0' || i == t->end)
return false;
else if (*s != t->source->buffer[i])
return false;
i++;
s++;
}
}
struct Parser
{
struct Source *source;
struct SourceLocation location;
int offset;
struct SourceLocation error_location;
const char *error;
bool fatal_error;
};
struct Parsed
{
const struct Source *source;
struct Trainer *trainers;
int trainers_n;
struct Stats default_ivs;
int default_ivs_line;
bool default_ivs_off;
int default_level;
int default_level_line;
bool default_level_off;
};
static bool set_parse_error(struct Parser *p, struct SourceLocation location, const char *error)
{
p->error = error;
p->error_location = location;
return false;
}
__attribute__((warn_unused_result))
static bool peek_char(struct Parser *p, unsigned char *c)
{
assert(p && c);
if (p->offset == p->source->buffer_n)
return false;
*c = p->source->buffer[p->offset];
return true;
}
__attribute__((warn_unused_result))
static bool pop_char(struct Parser *p, unsigned char *c)
{
assert(p && c);
if (p->offset == p->source->buffer_n)
return false;
*c = p->source->buffer[p->offset++];
if (*c == '\n')
{
p->location.line++;
p->location.column = 1;
}
else
{
p->location.column++;
}
return true;
}
static void skip_whitespace(struct Parser *p)
{
assert(p);
unsigned char c;
for (;;)
{
if (!peek_char(p, &c))
break;
if (c != ' ' && c != '\t')
break;
if (!pop_char(p, &c))
assert(false);
}
}
static void skip_line(struct Parser *p)
{
assert(p);
unsigned char c;
for (;;)
{
if (!pop_char(p, &c))
break;
if (c == '\n')
break;
}
}
__attribute__((warn_unused_result))
static bool match_eof(struct Parser *p)
{
return p->offset == p->source->buffer_n;
}
__attribute__((warn_unused_result))
static bool match_exact(struct Parser *p, const char *s)
{
assert(p && s);
struct Parser p_ = *p;
unsigned char c;
for (; *s != '\0'; s++)
{
if (!pop_char(&p_, &c))
return false;
if (*s != c)
return false;
}
*p = p_;
return true;
}
static void match_until_eol(struct Parser *p, struct Token *t)
{
assert(p && t);
unsigned char c;
skip_whitespace(p);
t->source = p->source;
t->location = p->location;
t->begin = p->offset;
t->end = p->offset;
for (;;)
{
if (!peek_char(p, &c))
break;
if (c == '\n')
break;
if (!pop_char(p, &c))
assert(false);
if (c != ' ' && c != '\t')
t->end = p->offset;
}
}
__attribute__((warn_unused_result))
static bool match_eol(struct Parser *p)
{
struct Parser p_ = *p;
unsigned char c;
skip_whitespace(&p_);
for (;;)
{
if (!pop_char(&p_, &c))
return false;
if (c == '\n')
break;
else
return false;
}
*p = p_;
return true;
}
__attribute__((warn_unused_result))
static bool match_int(struct Parser *p, int *i)
{
assert(p && i);
struct Parser p_ = *p;
unsigned char c;
*i = 0;
for (;;)
{
if (!peek_char(&p_, &c))
break;
if (!('0' <= c && c <= '9'))
break;
*i = *i * 10 + (c - '0');
if (!pop_char(&p_, &c))
assert(false);
}
if (p->offset == p_.offset)
return false;
*p = p_;
return true;
}
__attribute__((warn_unused_result))
static bool match_empty_line(struct Parser *p)
{
struct Parser p_ = *p;
if (match_exact(&p_, "# ")) {
int line;
if (match_int(&p_, &line)) {
struct Token t;
match_until_eol(&p_, &t);
p_.location.line = line - 1;
*p = p_;
}
}
return match_eol(p);
}
__attribute__((warn_unused_result))
static bool match_identifier(struct Parser *p, struct Token *t)
{
assert(p && t);
struct Parser p_ = *p;
unsigned char c;
t->source = p->source;
t->location = p->location;
t->begin = p->offset;
for (;;)
{
if (!peek_char(&p_, &c))
break;
if (('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z') || ('0' <= c && c <= '9') || c == '_')
;
else
break;
if (!pop_char(&p_, &c))
assert(false);
}
t->end = p_.offset;
if (t->begin == t->end)
return false;
*p = p_;
return true;
}
__attribute__((warn_unused_result))
static bool match_human_identifier(struct Parser *p, struct Token *t)
{
assert(p && t);
struct Parser p_ = *p, q = *p;
unsigned char c;
t->source = p->source;
t->location = p->location;
t->begin = p->offset;
t->end = p->offset;
for (;;)
{
if (!peek_char(&p_, &c))
break;
if (('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z') || c == '_' || c == '-' || c == ' ')
;
else if (p_.offset > t->begin && ('0' <= c && c <= '9'))
;
else
break;
if (!pop_char(&p_, &c))
assert(false);
if (c != ' ')
q = p_;
}
t->end = q.offset;
if (t->begin == t->end)
return false;
*p = q;
return true;
}
// Like 'match_human_identifier' but parses ':', for 'Type: Null', among
// other exceptions.
__attribute__((warn_unused_result))
static bool match_species_identifier(struct Parser *p, struct Token *t)
{
assert(p && t);
struct Parser p_ = *p, q = *p;
unsigned char c;
t->source = p->source;
t->location = p->location;
t->begin = p->offset;
t->end = p->offset;
for (;;)
{
if (!peek_char(&p_, &c))
break;
if (('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z') || ('0' <= c && c <= '9') || c == '_' || c == '-' || c == ' ' || c == ':' || c == '.' || c == '\'' || c >= 0x80)
;
else
break;
if (!pop_char(&p_, &c))
assert(false);
if (c != ' ')
q = p_;
}
t->end = q.offset;
if (t->begin == t->end)
return false;
*p = q;
return true;
}
// Like 'match_human_identifier' but parses '10,000,000 Volt Thunderbolt'
// among other exceptions.
__attribute__((warn_unused_result))
static bool match_move_identifier(struct Parser *p, struct Token *t)
{
assert(p && t);
struct Parser p_ = *p, q = *p;
unsigned char c;
t->source = p->source;
t->location = p->location;
t->begin = p->offset;
t->end = p->offset;
for (;;)
{
if (!peek_char(&p_, &c))
break;
if (('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z') || ('0' <= c && c <= '9') || c == '_' || c == '-' || c == ' ' || c == ',')
;
else
break;
if (!pop_char(&p_, &c))
assert(false);
if (c != ' ')
q = p_;
}
t->end = q.offset;
if (t->begin == t->end)
return false;
*p = q;
return true;
}
static bool show_parse_error(struct Parser *p)
{
// Print error message.
int n = fprintf(stderr, "%s:%d: ", p->source->path, p->error_location.line);
fprintf(stderr, "error: %s\n", p->error);
struct Parser p_ = {
.source = p->source,
.location = { .line = 1, .column = 1 },
.offset = 0,
};
for (;;) {
if (p->error_location.line == p_.location.line)
break;
if (!match_empty_line(&p_))
skip_line(&p_);
if (match_eof(&p_))
assert(false);
}
int begin = p_.offset;
int end;
for (end = begin; end < p->source->buffer_n; end++)
{
if (p->source->buffer[end] == '\n')
break;
}
// Print the source line.
fprintf(stderr, "%s:%d: %.*s\n", p->source->path, p->error_location.line, end - begin, &p->source->buffer[begin]);
// Print caret pointing at the column.
fprintf(stderr, "%*s", n, "");
for (int column = 1; column < p->error_location.column && begin + column < end; column++)
{
unsigned char c = p->source->buffer[begin + column];
fputc(c == '\t' ? c : ' ', stderr);
}
fprintf(stderr, "^\n");
p->error = NULL;
p->fatal_error = true;
return false;
}
static bool set_show_parse_error(struct Parser *p, struct SourceLocation location, const char *error)
{
set_parse_error(p, location, error);
return show_parse_error(p);
}
__attribute__((warn_unused_result))
static bool parse_section(struct Parser *p, struct Token *section)
{
assert(p && section);
struct Parser p_ = *p;
if (!match_exact(&p_, "==="))
return false;
skip_whitespace(&p_);
if (!match_identifier(&p_, section))
return set_parse_error(p, p_.location, "expected identifier");
skip_whitespace(&p_);
if (!match_exact(&p_, "==="))
return set_parse_error(p, p_.location, "expected '==='");
skip_whitespace(&p_);
if (!match_eol(&p_))
return set_parse_error(p, p_.location, "unexpected character in section");
*p = p_;
return true;
}
__attribute__((warn_unused_result))
static bool parse_pokemon_header(struct Parser *p, struct Token *nickname, struct Token *species, struct Token *gender, struct Token *item)
{
assert(p && nickname && species && gender && item);
struct Parser p_ = *p;
struct Token first = {}, second = {}, third = {};
if (!match_species_identifier(&p_, &first))
return false;
skip_whitespace(&p_);
if (match_exact(&p_, "("))
{
if (!match_species_identifier(&p_, &second))
return set_parse_error(p, p_.location, "expected species or gender");
if (!match_exact(&p_, ")"))
return set_parse_error(p, p_.location, "expected ')'");
skip_whitespace(&p_);
if (match_exact(&p_, "("))
{
if (!match_human_identifier(&p_, &third))
return set_parse_error(p, p_.location, "expected gender");
if (!match_exact(&p_, ")"))
return set_parse_error(p, p_.location, "expected ')'");
*nickname = first;
*species = second;
*gender = third;
}
else
{
if (second.begin + 1 == second.end) // Single letter.
{
*nickname = (struct Token) {};
*species = first;
*gender = second;
}
else
{
*nickname = first;
*species = second;
*gender = (struct Token) {};
}
}
}
else
{
*nickname = (struct Token) {};
*species = first;
*gender = (struct Token) {};
}
skip_whitespace(&p_);
if (match_exact(&p_, "@"))
{
skip_whitespace(&p_);
if (!match_human_identifier(&p_, item))
return set_parse_error(p, p_.location, "expected item");
}
else
{
*item = (struct Token) {};
}
skip_whitespace(&p_);
if (!match_eol(&p_))
return set_parse_error(p, p_.location, "unexpected character in Pokemon header");
*p = p_;
return true;
}
__attribute__((warn_unused_result))
static bool parse_pokemon_nature(struct Parser *p, struct Token *nature)
{
assert(p && nature);
struct Parser p_ = *p;
if (!match_identifier(&p_, nature))
return false;
skip_whitespace(&p_);
if (!match_exact(&p_, "Nature"))
return false;
skip_whitespace(&p_);
if (!match_eol(&p_))
return set_parse_error(p, p_.location, "unexpected character in nature");
*p = p_;
return true;
}
__attribute__((warn_unused_result))
static bool parse_attribute(struct Parser *p, struct Token *key, struct Token *value)
{
assert(p && key && value);
if (parse_pokemon_nature(p, value))
{
static const struct Source nature_source = { .path=NULL, .buffer=(unsigned char *)"Nature", .buffer_n=6 };
key->source = &nature_source;
key->location = p->location;
key->begin = 0;
key->end = nature_source.buffer_n;
return true;
}
struct Parser p_ = *p;
if (!match_human_identifier(&p_, key))
return false;
skip_whitespace(&p_);
if (match_exact(&p_, ":"))
{
skip_whitespace(&p_);
match_until_eol(&p_, value);
if (!match_eol(&p_))
return set_parse_error(p, p_.location, "unexpected character in attribute");
}
else
{
return set_parse_error(p, p_.location, "expected ':' or 'Nature'");
}
*p = p_;
return true;
}
__attribute__((warn_unused_result))
static bool parse_pokemon_move(struct Parser *p, struct Token *move)
{
assert(p && move);
struct Parser p_ = *p;
if (!match_exact(&p_, "-"))
return false;
skip_whitespace(&p_);
if (!match_move_identifier(&p_, move))
return set_parse_error(p, p_.location, "expected move");
skip_whitespace(&p_);
if (!match_eol(&p_))
return set_parse_error(p, p_.location, "unexpected character in move");
*p = p_;
return true;
}
static struct String token_string(const struct Token *t)
{
if (t->source)
{
return (struct String) {
.string = &t->source->buffer[t->begin],
.string_n = t->end - t->begin,
};
}
else
{
return (struct String) {
.string = NULL,
.string_n = 0,
};
}
}
static bool token_gender(struct Parser *p, const struct Token *t, enum Gender *g)
{
if (is_empty_token(t))
{
*g = GENDER_ANY;
return true;
}
else if (is_literal_token(t, "M") || is_literal_token(t, "Male"))
{
*g = GENDER_MALE;
return true;
}
else if (is_literal_token(t, "F") || is_literal_token(t, "Female"))
{
*g = GENDER_FEMALE;
return true;
}
else
{
return set_parse_error(p, t->location, "invalid gender");
}
}
static bool token_stats(struct Parser *p, const struct Token *t, struct Stats *stats, bool require_all)
{
struct Source source = {
.path = p->source->path,
.buffer = p->source->buffer,
.buffer_n = t->end,
};
struct Parser p_ = {
.source = &source,
.location = t->location,
.offset = t->begin,
};
bool has_hp = false, has_atk = false, has_def = false, has_spa = false, has_spd = false, has_spe = false;
for (;;)
{
int value;
skip_whitespace(&p_);
if (!match_int(&p_, &value))
return set_parse_error(p, p_.location, "expected integer");
struct Token stat;
skip_whitespace(&p_);
struct SourceLocation stat_location = p_.location;
if (!match_identifier(&p_, &stat))
return set_parse_error(p, p_.location, "expected 'HP', 'Atk', 'Def', 'SpA', 'SpD', or 'Spe'");
if (is_literal_token(&stat, "HP"))
{
stats->hp = value;
has_hp = true;
}
else if (is_literal_token(&stat, "Atk"))
{
stats->attack = value;
has_atk = true;
}
else if (is_literal_token(&stat, "Def"))
{
stats->defense = value;
has_def = true;
}
else if (is_literal_token(&stat, "SpA"))
{
stats->special_attack = value;
has_spa = true;
}
else if (is_literal_token(&stat, "SpD"))
{
stats->special_defense = value;
has_spd = true;
}
else if (is_literal_token(&stat, "Spe"))
{
stats->speed = value;
has_spe = true;
}
else
{
return set_parse_error(p, stat_location, "expected 'HP', 'Atk', 'Def', 'SpA', 'SpD', or 'Spe'");
}
skip_whitespace(&p_);
if (match_eof(&p_))
break;
else if (!match_exact(&p_, "/"))
return set_parse_error(p, p_.location, "expected '/' or newline");
}
if (require_all && !(has_hp && has_atk && has_def && has_spa && has_spd && has_spe))
return set_parse_error(p, p_.location, "expected all of 'HP', 'Atk', 'Def', 'SpA', 'SpD', and 'Spe'");
return true;
}
// '/'-separated strings.
static bool token_human_identifiers(struct Parser *p, const struct Token *t, struct String *ids, int *ids_n, int max_ids_n)
{
struct Source source = {
.path = p->source->path,
.buffer = p->source->buffer,
.buffer_n = t->end,
};
struct Parser p_ = {
.source = &source,
.location = t->location,
.offset = t->begin,
};
int n = 0;
while (*ids_n < max_ids_n)
{
skip_whitespace(&p_);
struct Token id;
if (!match_human_identifier(&p_, &id))
return set_parse_error(p, p_.location, "expected identifier");
ids[n++] = token_string(&id);
skip_whitespace(&p_);
if (match_eof(&p_))
break;
else if (!match_exact(&p_, "/"))
return set_parse_error(p, p_.location, "expected '/' or newline");
}
*ids_n = n;
return true;
}
static bool token_int(struct Parser *p, const struct Token *t, int *i)
{
char *end;
long l = strtol((const char *)&t->source->buffer[t->begin], &end, 10);
if ((unsigned char *)end != &t->source->buffer[t->end])
return set_parse_error(p, t->location, "invalid integer");
if (!(INT_MIN <= l && l <= INT_MAX))
return set_parse_error(p, t->location, "integer too large");
*i = l;
return true;
}
static bool token_bool(struct Parser *p, const struct Token *t, bool *b)
{
if (is_literal_token(t, "Yes"))
{
*b = true;
return true;
}
else if (is_literal_token(t, "No"))
{
*b = false;
return true;
}
else
{
return set_parse_error(p, t->location, "invalid boolean");
}
}
static bool parse_pragma(struct Parser *p, struct Parsed *parsed)
{
assert(p && parsed);
struct Parser p_ = *p;
if (!match_exact(&p_, "#pragma"))
return false;
// TODO: Require _a_ space between #pragma and trainerproc.
struct Token id;
skip_whitespace(&p_);
if (!match_identifier(&p_, &id))
return set_show_parse_error(p, p_.location, "expected 'trainerproc'");
if (!is_literal_token(&id, "trainerproc"))
return set_show_parse_error(p, id.location, "expected 'trainerproc'");
skip_whitespace(&p_);
if (!match_identifier(&p_, &id))
{
return set_show_parse_error(p, p_.location, "expected identifier");
}
else if (is_literal_token(&id, "ivs"))
{
if (parsed->default_ivs_line)
return set_show_parse_error(p, p_.location, "duplicate #pragma trainerproc ivs");
parsed->default_ivs_line = p_.location.line;
skip_whitespace(&p_);
struct Token t;
match_until_eol(&p_, &t);
if (is_literal_token(&t, "explicit"))
parsed->default_ivs_off = true;
else if (!token_stats(p, &t, &parsed->default_ivs, true))
return show_parse_error(p);
}
else if (is_literal_token(&id, "level"))
{
if (parsed->default_level_line)
return set_show_parse_error(p, p_.location, "duplicate #pragma trainerproc level");
parsed->default_level_line = p_.location.line;
skip_whitespace(&p_);
struct Token t;
match_until_eol(&p_, &t);
if (is_literal_token(&t, "explicit"))
parsed->default_level_off = true;
else if (!token_int(p, &t, &parsed->default_level))
return show_parse_error(p);
}
else
{
return set_show_parse_error(p, id.location, "unknown pragma");
}
if (!match_eol(&p_))
return set_show_parse_error(p, p_.location, "expected newline");
*p = p_;
return true;
}
// Species that are selected based on the explicit gender.
// e.g. 'Nidoran (M)' becomes 'Nidoran-M'.
static const struct {
const char *species;
const char *male_species;
const char *female_species;
} gendered_species[] = {
{ "Basculegion", "Basculegion-M", "Basculegion-F" },
{ "Indeedee", "Indeedee-M", "Indeedee-F" },
{ "Oinkologne", "Oinkologne-M", "Oinkologne-F" },
{ "Meowstic", "Meowstic-M", "Meowstic-F" },
{ "Nidoran", "Nidoran-M", "Nidoran-F" },
{ NULL, NULL, NULL }
};
// Items that are selected based on the explicit form.
// e.g. 'Arceus-Fire' becomes 'Arceus @ Flame Plate'.
static const struct {
const char *form;
const char *species;
const char *item;
} itemed_species[] = {
{ "Arceus-Bug", "Arceus", "Insect Plate" },
{ "Arceus-Dark", "Arceus", "Dread Plate" },
{ "Arceus-Dragon", "Arceus", "Draco Plate" },
{ "Arceus-Electric", "Arceus", "Zap Plate" },
{ "Arceus-Fairy", "Arceus", "Pixie Plate" },
{ "Arceus-Fighting", "Arceus", "Fist Plate" },
{ "Arceus-Fire", "Arceus", "Flame Plate" },
{ "Arceus-Flying", "Arceus", "Sky Plate" },
{ "Arceus-Ghost", "Arceus", "Spooky Plate" },
{ "Arceus-Grass", "Arceus", "Meadow Plate" },
{ "Arceus-Ground", "Arceus", "Earth Plate" },
{ "Arceus-Ice", "Arceus", "Icicle Plate" },
{ "Arceus-Poison", "Arceus", "Toxic Plate" },
{ "Arceus-Psychic", "Arceus", "Mind Plate" },
{ "Arceus-Rock", "Arceus", "Stone Plate" },
{ "Arceus-Steel", "Arceus", "Iron Plate" },
{ "Arceus-Water", "Arceus", "Splash Plate" },
{ "Genesect-Burn", "Genesect", "Burn Drive" },
{ "Genesect-Chill", "Genesect", "Chill Drive" },
{ "Genesect-Douse", "Genesect", "Douse Drive" },
{ "Genesect-Shock", "Genesect", "Shock Drive" },
{ "Ogerpon-Cornerstone", "Ogerpon", "Cornerstone Mask" },
{ "Ogerpon-Hearthflame", "Ogerpon", "Hearthflame Mask" },
{ "Ogerpon-Wellspring", "Ogerpon", "Wellspring Mask" },
{ "Silvally-Bug", "Silvally", "Bug Memory" },
{ "Silvally-Dark", "Silvally", "Dark Memory" },
{ "Silvally-Dragon", "Silvally", "Dragon Memory" },
{ "Silvally-Electric", "Silvally", "Electric Memory" },
{ "Silvally-Fairy", "Silvally", "Fairy Memory" },
{ "Silvally-Fighting", "Silvally", "Fighting Memory" },
{ "Silvally-Fire", "Silvally", "Fire Memory" },
{ "Silvally-Flying", "Silvally", "Flying Memory" },
{ "Silvally-Ghost", "Silvally", "Ghost Memory" },
{ "Silvally-Grass", "Silvally", "Grass Memory" },
{ "Silvally-Ground", "Silvally", "Ground Memory" },
{ "Silvally-Ice", "Silvally", "Ice Memory" },
{ "Silvally-Poison", "Silvally", "Poison Memory" },
{ "Silvally-Psychic", "Silvally", "Psychic Memory" },
{ "Silvally-Rock", "Silvally", "Rock Memory" },
{ "Silvally-Steel", "Silvally", "Steel Memory" },
{ "Silvally-Water", "Silvally", "Water Memory" },
{ NULL, NULL, NULL }
};
static bool parse_trainer(struct Parser *p, const struct Parsed *parsed, struct Trainer *trainer)
{
bool any_error = false;
*trainer = (struct Trainer) {};
while (match_empty_line(p)) {}
struct Token id;
struct Token nickname, species, gender, item;
if (!parse_section(p, &id))
{
if (!p->error)
set_parse_error(p, p->location, "expected '==='");
show_parse_error(p);
return false;
}
trainer->id = token_string(&id);
trainer->id_line = id.location.line;
// Parse trainer attributes.
struct Token key, value;
while (parse_attribute(p, &key, &value))
{
if (is_literal_token(&key, "AI"))
{
if (trainer->ai_flags_line)
any_error = !set_show_parse_error(p, key.location, "duplicate 'AI'");
trainer->ai_flags_line = value.location.line;
if (!token_human_identifiers(p, &value, trainer->ai_flags, &trainer->ai_flags_n, MAX_TRAINER_AI_FLAGS))
any_error = !show_parse_error(p);
}
else if (is_literal_token(&key, "Items"))
{
if (trainer->items_line)
any_error = !set_show_parse_error(p, key.location, "duplicate 'Items'");
trainer->items_line = value.location.line;
if (!token_human_identifiers(p, &value, trainer->items, &trainer->items_n, MAX_TRAINER_ITEMS))
any_error = !show_parse_error(p);
}
else if (is_literal_token(&key, "Class"))
{
if (trainer->class_line)
any_error = !set_show_parse_error(p, key.location, "duplicate 'Class'");
trainer->class_line = value.location.line;
trainer->class = token_string(&value);
}
else if (is_literal_token(&key, "Music"))
{
if (trainer->encounter_music_line)
any_error = !set_show_parse_error(p, key.location, "duplicate 'Music'");
trainer->encounter_music_line = value.location.line;
trainer->encounter_music = token_string(&value);
}
else if (is_literal_token(&key, "Gender"))
{
if (trainer->gender_line)
any_error = !set_show_parse_error(p, key.location, "duplicate 'Gender'");
trainer->gender_line = value.location.line;
if (!token_gender(p, &value, &trainer->gender))
any_error = !show_parse_error(p);
}
else if (is_literal_token(&key, "Pic"))
{
if (trainer->pic_line)
any_error = !set_show_parse_error(p, key.location, "duplicate 'Pic'");
trainer->pic_line = value.location.line;
trainer->pic = token_string(&value);
}
else if (is_literal_token(&key, "Name"))
{
if (trainer->name_line)
any_error = !set_show_parse_error(p, key.location, "duplicate 'Name'");
trainer->name_line = value.location.line;
trainer->name = token_string(&value);
}
else if (is_literal_token(&key, "Double Battle"))
{
if (trainer->double_battle_line)
any_error = !set_show_parse_error(p, key.location, "duplicate 'Double Battle'");
trainer->double_battle_line = value.location.line;
if (!token_bool(p, &value, &trainer->double_battle))
any_error = !show_parse_error(p);
}
else if (is_literal_token(&key, "Mugshot"))
{
if (trainer->mugshot_line)
any_error = !set_show_parse_error(p, key.location, "duplicate 'Mugshot'");
trainer->mugshot_line = value.location.line;
trainer->mugshot = token_string(&value);
}
else if (is_literal_token(&key, "Starting Status"))
{
if (trainer->starting_status_line)
any_error = !set_show_parse_error(p, key.location, "duplicate 'Starting Status'");
trainer->starting_status_line = value.location.line;
trainer->starting_status = token_string(&value);
}
else
{
any_error = !set_show_parse_error(p, key.location, "expected one of 'Name', 'Class', 'Pic', 'Gender', 'Music', 'Items', 'Double Battle', or 'AI'");
}
}
if (!trainer->pic_line)
any_error = !set_show_parse_error(p, p->location, "expected 'Pic' before Pokemon");
if (!trainer->name_line)
any_error = !set_show_parse_error(p, p->location, "expected 'Name' before Pokemon");
if (!match_empty_line(p))
{
set_show_parse_error(p, p->location, "expected empty line");
// Continue if the line is the start of a new Pokemon.
struct Parser p_ = *p;
if (!parse_pokemon_header(&p_, &nickname, &species, &gender, &item))
return false;
}
for (int i = 0; i < PARTY_SIZE; i++)
{
struct Pokemon *pokemon = &trainer->pokemon[i];
// Parse first line.
while (match_empty_line(p)) {}
if (!parse_pokemon_header(p, &nickname, &species, &gender, &item))
{
if (i > 0 || ends_with(trainer->id, "_NONE"))
break;
if (!p->error)
set_parse_error(p, p->location, "expected nickname or species");
show_parse_error(p);
// TODO: Continue if a Pokemon starts before the next section.
// This is difficult to do, because 'Type: Null' is the name
// of a species, and so 'X: Y' is being parsed as a Pokemon
// header.
return false;
}
trainer->pokemon_n++;
pokemon->nickname = token_string(&nickname);
pokemon->species = token_string(&species);
if (!token_gender(p, &gender, &pokemon->gender))
any_error = !show_parse_error(p);
pokemon->item = token_string(&item);
pokemon->header_line = species.location.line;
for (int i = 0; gendered_species[i].species; i++)
{
if (is_literal_string(pokemon->species, gendered_species[i].species))
{
switch (pokemon->gender)
{
case GENDER_ANY:
break;
case GENDER_MALE:
pokemon->species = literal_string(gendered_species[i].male_species);
break;
case GENDER_FEMALE:
pokemon->species = literal_string(gendered_species[i].female_species);
break;
}
pokemon->gender = GENDER_ANY;
break;
}
}
for (int i = 0; itemed_species[i].species; i++)
{
if (is_literal_string(pokemon->species, itemed_species[i].form)
&& is_empty_string(pokemon->item))
{
pokemon->species = literal_string(itemed_species[i].species);
pokemon->item = literal_string(itemed_species[i].item);
}
}
// Parse Pokemon attributes.
while (parse_attribute(p, &key, &value))
{
if (is_literal_token(&key, "EVs"))
{
if (pokemon->evs_line)
any_error = !set_show_parse_error(p, key.location, "duplicate 'EVs'");
pokemon->evs_line = value.location.line;
if (!token_stats(p, &value, &pokemon->evs, false))
any_error = !show_parse_error(p);
}
else if (is_literal_token(&key, "IVs"))
{
if (pokemon->ivs_line)
any_error = !set_show_parse_error(p, key.location, "duplicate 'IVs'");
pokemon->ivs_line = value.location.line;
pokemon->ivs = parsed->default_ivs;
if (!token_stats(p, &value, &pokemon->ivs, parsed->default_ivs_off))
any_error = !show_parse_error(p);
}
else if (is_literal_token(&key, "Ability"))
{
if (pokemon->ability_line)
any_error = !set_show_parse_error(p, key.location, "duplicate 'Ability'");
pokemon->ability_line = value.location.line;
pokemon->ability = token_string(&value);
}
else if (is_literal_token(&key, "Level"))
{
if (pokemon->level_line)
any_error = !set_show_parse_error(p, key.location, "duplicate 'Level'");
pokemon->level_line = value.location.line;
if (!token_int(p, &value, &pokemon->level))
any_error = !show_parse_error(p);
}
else if (is_literal_token(&key, "Ball"))
{
if (pokemon->ball_line)
any_error = !set_show_parse_error(p, key.location, "duplicate 'Ball'");
pokemon->ball_line = value.location.line;
pokemon->ball = token_string(&value);
}
else if (is_literal_token(&key, "Happiness"))
{
if (pokemon->friendship_line)
any_error = !set_show_parse_error(p, key.location, "duplicate 'Happiness'");
pokemon->friendship_line = value.location.line;
if (!token_int(p, &value, &pokemon->friendship))
any_error = !show_parse_error(p);
}
else if (is_literal_token(&key, "Nature"))
{
if (pokemon->nature_line)
any_error = !set_show_parse_error(p, value.location, "duplicate 'Nature'");
pokemon->nature_line = value.location.line;
pokemon->nature = token_string(&value);
}
else if (is_literal_token(&key, "Shiny"))
{
if (pokemon->shiny_line)
any_error = !set_show_parse_error(p, key.location, "duplicate 'Shiny'");
pokemon->shiny_line = value.location.line;
if (!token_bool(p, &value, &pokemon->shiny))
any_error = !show_parse_error(p);
}
else if (is_literal_token(&key, "Dynamax Level"))
{
if (pokemon->dynamax_level_line)
any_error = !set_show_parse_error(p, key.location, "duplicate 'Dynamax Level'");
pokemon->dynamax_level_line = value.location.line;
if (!token_int(p, &value, &pokemon->dynamax_level))
any_error = !show_parse_error(p);
}
else if (is_literal_token(&key, "Gigantamax"))
{
if (pokemon->gigantamax_factor_line)
any_error = !set_show_parse_error(p, key.location, "duplicate 'Gigantamax'");
pokemon->gigantamax_factor_line = value.location.line;
if (!token_bool(p, &value, &pokemon->gigantamax_factor))
any_error = !show_parse_error(p);
}
else if (is_literal_token(&key, "Tera Type"))
{
if (pokemon->tera_type_line)
any_error = !set_show_parse_error(p, key.location, "duplicate 'Tera Type'");
pokemon->tera_type_line = value.location.line;
pokemon->tera_type = token_string(&value);
}
else
{
any_error = !set_show_parse_error(p, key.location, "expected one of 'EVs', 'IVs', 'Ability', 'Level', 'Ball', 'Happiness', 'Nature', 'Shiny', 'Dynamax Level', 'Gigantamax', or 'Tera Type'");
}
}
if (!pokemon->level_line)
{
if (!parsed->default_level_off)
{
pokemon->level = parsed->default_level;
pokemon->level_line = p->location.line;
}
else
{
any_error = !set_show_parse_error(p, p->location, "expected 'Level' before moves");
}
}
if (!pokemon->ivs_line)
{
if (!parsed->default_ivs_off)
{
pokemon->ivs = parsed->default_ivs;
pokemon->ivs_line = p->location.line;
}
else
{
any_error = !set_show_parse_error(p, p->location, "expected 'IVs' before moves");
}
}
// Parse moves.
for (int j = 0; j < MAX_MON_MOVES; j++)
{
struct Token move;
if (!parse_pokemon_move(p, &move))
{
struct Parser p_ = *p;
if (match_eof(&p_) || match_empty_line(&p_))
break;
}
else
{
if (pokemon->moves_n == 0)
pokemon->move1_line = move.location.line;
pokemon->moves[pokemon->moves_n] = token_string(&move);
pokemon->moves_n++;
}
}
if (match_eof(p))
break;
if (!match_empty_line(p))
{
set_show_parse_error(p, p->location, "expected empty line");
// Continue if the line is the start of a new Pokemon.
struct Parser p_ = *p;
if (!parse_pokemon_header(&p_, &nickname, &species, &gender, &item))
return false;
}
}
return !any_error;
}
static void parse(struct Parser *p, struct Parsed *parsed)
{
parsed->source = p->source;
int trainers_c = 256;
parsed->trainers = malloc(sizeof(*parsed->trainers) * trainers_c);
parsed->trainers_n = 0;
assert(parsed->trainers);
for (;;)
{
while (match_empty_line(p)) {}
if (!parse_pragma(p, parsed))
break;
}
for (;;)
{
if (parsed->trainers_n == trainers_c)
{
trainers_c *= 2;
struct Trainer *trainers_ = realloc(parsed->trainers, sizeof(*parsed->trainers) * trainers_c);
assert(trainers_);
parsed->trainers = trainers_;
}
struct Trainer *trainer = &parsed->trainers[parsed->trainers_n];
while (match_empty_line(p)) {}
if (match_eof(p))
break;
if (!parse_trainer(p, parsed, trainer))
{
struct Token t;
// Skip to the next trainer.
for (;;)
{
struct Parser p_ = *p;
if (match_eof(&p_) || parse_section(&p_, &t))
break;
skip_line(p);
}
}
else
{
parsed->trainers_n++;
}
}
}
static void fprint_string(FILE *f, struct String s)
{
fprintf(f, "%.*s", s.string_n, s.string);
}
static void fprint_bool(FILE *f, bool b)
{
fprintf(f, b ? "TRUE" : "FALSE");
}
static void fprint_stats(FILE *f, const char *macro, struct Stats stats)
{
fprintf(f, "%s(%d, %d, %d, %d, %d, %d)", macro, stats.hp, stats.attack, stats.defense, stats.speed, stats.special_attack, stats.special_defense);
}
static bool is_constant(struct String s, const char *prefix)
{
for (int i = 0;; i++)
{
if (i == s.string_n)
return false;
else if (prefix[i] == '\0')
return s.string[i] == '_';
else if (prefix[i] != s.string[i])
return false;
}
}
static void fprint_constant(FILE *f, const char *prefix, struct String s)
{
if (!is_constant(s, prefix)) fprintf(f, "%s_", prefix);
if (s.string_n > 0)
{
for (int i = 0; i < s.string_n; i++)
{
unsigned char c = s.string[i];
if (('A' <= c && c <= 'Z') || ('0' <= c && c <= '9'))
fputc(c, f);
else if ('a' <= c && c <= 'z')
fputc(c - 'a' + 'A', f);
else
fputc('_', f);
}
}
else
{
fprintf(f, "NONE");
}
}
// This is a really stupid helper for 'fprint_species'.
static bool is_utf8_character(struct String s, int *i, const unsigned char *utf8)
{
for (int j = *i;; j++, utf8++)
{
if (*utf8 == '\0')
{
*i = j - 1; // '- 1' because of the '++' in 'fprint_species'.
return true;
}
else if (j == s.string_n || s.string[j] != *utf8)
{
return false;
}
}
}
static void fprint_species(FILE *f, const char *prefix, struct String s)
{
if (!is_constant(s, prefix)) fprintf(f, "%s_", prefix);
if (s.string_n == 0)
{
fprintf(f, "NONE");
}
else
{
bool underscore = false;
static const unsigned char *male = (unsigned char *)u8"";
static const unsigned char *female = (unsigned char *)u8"";
static const unsigned char *e_diacritic = (unsigned char *)u8"é";
static const unsigned char *right_single_quotation_mark = (unsigned char *)u8"";
for (int i = 0; i < s.string_n; i++)
{
unsigned char c = s.string[i];
if (('A' <= c && c <= 'Z') || ('0' <= c && c <= '9'))
{
if (underscore)
fputc('_', f);
underscore = false;
fputc(c, f);
}
else if ('a' <= c && c <= 'z')
{
if (underscore)
fputc('_', f);
underscore = false;
fputc(c - 'a' + 'A', f);
}
else if (c == '\'' || c == '%' || is_utf8_character(s, &i, right_single_quotation_mark))
{
// Do nothing.
}
else if (is_utf8_character(s, &i, male))
{
underscore = false;
fprintf(f, "_M");
}
else if (is_utf8_character(s, &i, female))
{
underscore = false;
fprintf(f, "_F");
}
else if (is_utf8_character(s, &i, e_diacritic))
{
if (underscore)
fputc('_', f);
underscore = false;
fputc('E', f);
}
else
{
underscore = true;
}
}
}
}
static void fprint_trainers(const char *output_path, FILE *f, struct Parsed *parsed)
{
fprintf(f, "//\n");
fprintf(f, "// DO NOT MODIFY THIS FILE! It is auto-generated from %s\n", parsed->source->path);
fprintf(f, "//\n");
fprintf(f, "// If you want to modify this file set COMPETITIVE_PARTY_SYNTAX to FALSE\n");
fprintf(f, "// in include/config/general.h and remove this notice.\n");
fprintf(f, "// Use sed -i '/^#line/d' '%s' to remove #line markers.\n", output_path);
fprintf(f, "//\n");
fprintf(f, "\n");
fprintf(f, "#line 1 \"%s\"\n", parsed->source->path);
fprintf(f, "\n");
for (int i = 0; i < parsed->trainers_n; i++)
{
struct Trainer *trainer = &parsed->trainers[i];
fprintf(f, "#line %d\n", trainer->id_line);
fprintf(f, " [");
fprint_string(f, trainer->id);
fprintf(f, "] =\n");
fprintf(f, " {\n");
if (!is_empty_string(trainer->name))
{
fprintf(f, "#line %d\n", trainer->name_line);
fprintf(f, " .trainerName = _(\"");
fprint_string(f, trainer->name);
fprintf(f, "\"),\n");
}
if (!is_empty_string(trainer->class))
{
fprintf(f, "#line %d\n", trainer->class_line);
fprintf(f, " .trainerClass = ");
fprint_constant(f, "TRAINER_CLASS", trainer->class);
fprintf(f, ",\n");
}
if (!is_empty_string(trainer->pic))
{
fprintf(f, "#line %d\n", trainer->pic_line);
fprintf(f, " .trainerPic = ");
if (starts_with(trainer->id, "PARTNER_"))
fprint_constant(f, "TRAINER_BACK_PIC", trainer->pic);
else
fprint_constant(f, "TRAINER_PIC", trainer->pic);
fprintf(f, ",\n");
}
fprintf(f, " .encounterMusic_gender = \n");
if (trainer->gender == GENDER_FEMALE)
{
fprintf(f, "#line %d\n", trainer->gender_line);
fprintf(f, "F_TRAINER_FEMALE | \n");
}
if (!is_empty_string(trainer->encounter_music))
{
fprintf(f, "#line %d\n", trainer->encounter_music_line);
fprintf(f, " ");
fprint_constant(f, "TRAINER_ENCOUNTER_MUSIC", trainer->encounter_music);
}
else
{
fprintf(f, "0");
}
fprintf(f, ",\n");
if (trainer->items_n > 0)
{
fprintf(f, "#line %d\n", trainer->items_line);
fprintf(f, " .items = { ");
for (int i = 0; i < trainer->items_n; i++)
{
if (i > 0)
fprintf(f, ", ");
fprint_constant(f, "ITEM", trainer->items[i]);
}
fprintf(f, " },\n");
}
if (trainer->double_battle_line)
{
fprintf(f, "#line %d\n", trainer->double_battle_line);
fprintf(f, " .doubleBattle = ");
fprint_bool(f, trainer->double_battle);
fprintf(f, ",\n");
}
if (trainer->ai_flags_n > 0)
{
fprintf(f, "#line %d\n", trainer->ai_flags_line);
fprintf(f, " .aiFlags = ");
for (int i = 0; i < trainer->ai_flags_n; i++)
{
if (i > 0)
fprintf(f, " | ");
fprint_constant(f, "AI_FLAG", trainer->ai_flags[i]);
}
fprintf(f, ",\n");
}
if (!is_empty_string(trainer->mugshot))
{
fprintf(f, "#line %d\n", trainer->mugshot_line);
fprintf(f, " .mugshotEnabled = TRUE,\n");
fprintf(f, " .mugshotColor = ");
fprint_constant(f, "MUGSHOT_COLOR", trainer->mugshot);
fprintf(f, ",\n");
}
if (!is_empty_string(trainer->starting_status))
{
fprintf(f, "#line %d\n", trainer->starting_status_line);
fprintf(f, " .startingStatus = ");
fprint_constant(f, "STARTING_STATUS", trainer->starting_status);
fprintf(f, ",\n");
}
fprintf(f, " .partySize = %d,\n", trainer->pokemon_n);
fprintf(f, " .party = (const struct TrainerMon[])\n");
fprintf(f, " {\n");
for (int j = 0; j < trainer->pokemon_n; j++)
{
struct Pokemon *pokemon = &trainer->pokemon[j];
fprintf(f, " {\n");
if (!is_empty_string(pokemon->nickname))
{
fprintf(f, "#line %d\n", pokemon->header_line);
fprintf(f, " .nickname = COMPOUND_STRING(\"");
fprint_string(f, pokemon->nickname);
fprintf(f, "\"),\n");
}
fprintf(f, "#line %d\n", pokemon->header_line);
fprintf(f, " .species = ");
fprint_species(f, "SPECIES", pokemon->species);
fprintf(f, ",\n");
switch (pokemon->gender)
{
case GENDER_ANY:
fprintf(f, " .gender = TRAINER_MON_RANDOM_GENDER,\n");
break;
case GENDER_MALE:
fprintf(f, "#line %d\n", pokemon->header_line);
fprintf(f, " .gender = TRAINER_MON_MALE,\n");
break;
case GENDER_FEMALE:
fprintf(f, "#line %d\n", pokemon->header_line);
fprintf(f, " .gender = TRAINER_MON_FEMALE,\n");
break;
}
if (!is_empty_string(pokemon->item))
{
fprintf(f, "#line %d\n", pokemon->header_line);
fprintf(f, " .heldItem = ");
fprint_constant(f, "ITEM", pokemon->item);
fprintf(f, ",\n");
}
if (pokemon->evs_line)
{
fprintf(f, "#line %d\n", pokemon->evs_line);
fprintf(f, " .ev = ");
fprint_stats(f, "TRAINER_PARTY_EVS", pokemon->evs);
fprintf(f, ",\n");
}
if (pokemon->ivs_line)
{
fprintf(f, "#line %d\n", pokemon->ivs_line);
fprintf(f, " .iv = ");
fprint_stats(f, "TRAINER_PARTY_IVS", pokemon->ivs);
fprintf(f, ",\n");
}
if (pokemon->ability_line)
{
fprintf(f, "#line %d\n", pokemon->ability_line);
fprintf(f, " .ability = ");
fprint_constant(f, "ABILITY", pokemon->ability);
fprintf(f, ",\n");
}
if (pokemon->level_line)
{
fprintf(f, "#line %d\n", pokemon->level_line);
fprintf(f, " .lvl = %d,\n", pokemon->level);
}
if (pokemon->ball_line)
{
fprintf(f, "#line %d\n", pokemon->ball_line);
fprintf(f, " .ball = ");
fprint_constant(f, "ITEM", pokemon->ball);
fprintf(f, ",\n");
}
if (pokemon->friendship_line)
{
fprintf(f, "#line %d\n", pokemon->friendship_line);
fprintf(f, " .friendship = %d,\n", pokemon->friendship);
}
if (pokemon->nature_line)
{
fprintf(f, "#line %d\n", pokemon->nature_line);
fprintf(f, " .nature = ");
fprint_constant(f, "NATURE", pokemon->nature);
fprintf(f, ",\n");
}
else
{
fprintf(f, " .nature = NATURE_HARDY,\n");
}
if (pokemon->shiny_line)
{
fprintf(f, "#line %d\n", pokemon->shiny_line);
fprintf(f, " .isShiny = ");
fprint_bool(f, pokemon->shiny);
fprintf(f, ",\n");
}
if (pokemon->dynamax_level_line)
{
fprintf(f, "#line %d\n", pokemon->dynamax_level_line);
fprintf(f, " .dynamaxLevel = %d,\n", pokemon->dynamax_level);
}
else
{
fprintf(f, " .dynamaxLevel = MAX_DYNAMAX_LEVEL,\n");
}
if (pokemon->gigantamax_factor_line)
{
fprintf(f, "#line %d\n", pokemon->gigantamax_factor_line);
fprintf(f, " .gigantamaxFactor = ");
fprint_bool(f, pokemon->gigantamax_factor);
fprintf(f, ",\n");
}
if (pokemon->dynamax_level_line || pokemon->gigantamax_factor_line)
{
fprintf(f, " .shouldUseDynamax = TRUE,\n");
}
else if (pokemon->tera_type_line)
{
fprintf(f, "#line %d\n", pokemon->tera_type_line);
fprintf(f, " .teraType = ");
fprint_constant(f, "TYPE", pokemon->tera_type);
fprintf(f, ",\n");
}
if (pokemon->moves_n > 0)
{
fprintf(f, " .moves = {\n");
fprintf(f, "#line %d\n", pokemon->move1_line);
for (int k = 0; k < pokemon->moves_n; k++)
{
fprintf(f, " ");
fprint_constant(f, "MOVE", pokemon->moves[k]);
fprintf(f, ",\n");
}
fprintf(f, " },\n");
}
fprintf(f, " },\n");
}
fprintf(f, " },\n");
fprintf(f, " },\n");
}
}
static void usage(FILE *file, char *argv0)
{
fprintf(file, "Usage: %s -o <output> <source>\n", argv0);
}
int main(int argc, char *argv[])
{
int status = 1;
FILE *source_file = NULL;
FILE *output_file = NULL;
unsigned char *source_buffer = NULL;
struct Parsed parsed = {
.default_ivs = { 31, 31, 31, 31, 31, 31 },
.default_level = 100,
};
const char *source_path = NULL;
const char *output_path = NULL;
const char *real_source_path = NULL;
int opt;
while ((opt = getopt(argc, argv, "i:o:")) != -1)
{
switch (opt)
{
case 'i':
real_source_path = optarg;
break;
case 'o':
output_path = optarg;
break;
default:
fprintf(stderr, "unknown option '%c'\n", opt);
usage(stderr, argv[0]);
goto exit;
}
}
if (!output_path)
{
usage(stderr, argv[0]);
goto exit;
}
if (optind != argc - 1)
{
usage(stderr, argv[0]);
goto exit;
}
source_path = argv[optind++];
int source_buffer_n;
if (strcmp(source_path, "-") == 0)
{
source_file = stdin;
source_path = "<stdin>";
int source_buffer_c = 4096;
source_buffer_n = 0;
for (;;)
{
if (!(source_buffer = realloc(source_buffer, source_buffer_c)))
{
fprintf(stderr, "could not allocate %d bytes\n", source_buffer_c);
goto exit;
}
source_buffer_n += fread(&source_buffer[source_buffer_n], 1, source_buffer_c - source_buffer_n, source_file);
if (source_buffer_n < source_buffer_c)
break;
source_buffer_c += source_buffer_n / 2; // 1.5x growth rate.
}
}
else
{
source_file = fopen(source_path, "r");
if (source_file == NULL)
{
fprintf(stderr, "could not open '%s' for reading\n", source_path);
goto exit;
}
fseek(source_file, 0, SEEK_END);
long source_buffer_n_ = ftell(source_file);
if (source_buffer_n_ > INT_MAX)
{
fprintf(stderr, "could not read '%s': too big\n", source_path);
goto exit;
}
source_buffer_n = source_buffer_n_;
if (!(source_buffer = malloc(source_buffer_n)))
{
fprintf(stderr, "could not allocate %d bytes\n", source_buffer_n);
goto exit;
}
rewind(source_file);
if (fread(source_buffer, 1, source_buffer_n, source_file) < source_buffer_n)
{
fprintf(stderr, "could not read '%s'\n", source_path);
goto exit;
}
}
struct Source source = {
.path = real_source_path ? real_source_path : source_path,
.buffer = source_buffer,
.buffer_n = source_buffer_n,
};
struct Parser parser = {
.source = &source,
.location = { .line = 1, .column = 1 },
.offset = 0,
};
parse(&parser, &parsed);
if (parser.fatal_error)
{
goto exit;
}
if (strcmp(output_path, "-") == 0)
{
source_file = stdout;
output_path = "<stdout>";
}
else
{
output_file = fopen(output_path, "w");
if (output_file == NULL)
{
fprintf(stderr, "could not open '%s' for writing\n", output_path);
goto exit;
}
}
fprint_trainers(output_path, output_file, &parsed);
status = 0;
exit:
if (output_file) fclose(output_file);
if (parsed.trainers) free(parsed.trainers);
if (source_buffer) free(source_buffer);
if (source_file) fclose(source_file);
return status;
}