mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-10 04:31:34 -10:00
302 lines
8.9 KiB
C++
302 lines
8.9 KiB
C++
/*
|
|
* Created by Hassan "Karajorma" Kazmi and Josh "jg18" Glatt for The FreeSpace 2 Source Code Project.
|
|
* You may not sell or otherwise commercially exploit the source or things you
|
|
* create based on the source.
|
|
*/
|
|
|
|
#include <algorithm>
|
|
#include <iterator>
|
|
|
|
#include "globalincs/toolchain.h"
|
|
#include "parse/generic_log.h"
|
|
#include "parse/parselo.h"
|
|
#include "parse/sexp_container.h"
|
|
|
|
static SCP_vector<sexp_container> Sexp_containers;
|
|
static SCP_unordered_map<SCP_string, sexp_container*, SCP_string_lcase_hash, SCP_string_lcase_equal_to>
|
|
Containers_by_name_map;
|
|
|
|
|
|
const SCP_vector<sexp_container> &get_all_sexp_containers()
|
|
{
|
|
return Sexp_containers;
|
|
}
|
|
|
|
static const SCP_vector<list_modifier> List_modifiers = {
|
|
{ "Get_First", ListModifier::GET_FIRST },
|
|
{ "Get_Last", ListModifier::GET_LAST },
|
|
{ "Remove_First", ListModifier::REMOVE_FIRST },
|
|
{ "Remove_Last", ListModifier::REMOVE_LAST },
|
|
{ "Get_Random", ListModifier::GET_RANDOM },
|
|
{ "Remove_Random", ListModifier::REMOVE_RANDOM },
|
|
{ "At", ListModifier::AT_INDEX }
|
|
};
|
|
|
|
const SCP_vector<list_modifier>& get_all_list_modifiers()
|
|
{
|
|
return List_modifiers;
|
|
}
|
|
|
|
// sexp_container functions
|
|
|
|
bool sexp_container::operator==(const sexp_container &sc) const
|
|
{
|
|
return container_name == sc.container_name && type == sc.type && opf_type == sc.opf_type &&
|
|
list_data == sc.list_data && map_data == sc.map_data;
|
|
}
|
|
|
|
bool sexp_container::name_matches(const sexp_container &container) const
|
|
{
|
|
return !stricmp(container.container_name.c_str(), container_name.c_str());
|
|
}
|
|
|
|
bool sexp_container::empty() const
|
|
{
|
|
return size() == 0;
|
|
}
|
|
|
|
int sexp_container::size() const
|
|
{
|
|
if (is_list()) {
|
|
return (int)list_data.size();
|
|
} else if (is_map()) {
|
|
return (int)map_data.size();
|
|
} else {
|
|
UNREACHABLE("Unknown container type %d", (int)type);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static constexpr ContainerType CONTAINER_PERSISTENCE_FLAGS =
|
|
ContainerType::SAVE_ON_MISSION_CLOSE | ContainerType::SAVE_ON_MISSION_PROGRESS | ContainerType::NETWORK |
|
|
ContainerType::SAVE_TO_PLAYER_FILE;
|
|
|
|
ContainerType sexp_container::get_non_persistent_type() const
|
|
{
|
|
return type & ~CONTAINER_PERSISTENCE_FLAGS;
|
|
}
|
|
|
|
bool sexp_container::type_matches(const sexp_container &container) const
|
|
{
|
|
return get_non_persistent_type() == container.get_non_persistent_type();
|
|
}
|
|
|
|
// sexp_container.h functions
|
|
|
|
/**
|
|
* Clear the SEXP Containers and related data
|
|
*/
|
|
void init_sexp_containers()
|
|
{
|
|
Sexp_containers.clear();
|
|
Containers_by_name_map.clear();
|
|
}
|
|
|
|
void update_sexp_containers(SCP_vector<sexp_container> &containers)
|
|
{
|
|
Sexp_containers = std::move(containers);
|
|
|
|
Containers_by_name_map.clear();
|
|
for (auto &container : Sexp_containers) {
|
|
Containers_by_name_map.emplace(container.container_name, &container);
|
|
}
|
|
}
|
|
|
|
bool stuff_one_generic_sexp_container(SCP_string &name, ContainerType &type, int &opf_type, SCP_vector<SCP_string> &data)
|
|
{
|
|
bool valid = true; // try to skip past bad containers by not returning until end of function
|
|
SCP_string temp_type_string;
|
|
|
|
data.clear();
|
|
|
|
required_string("$Name:");
|
|
stuff_string(name, F_NAME);
|
|
|
|
if (name.empty()) {
|
|
Warning(LOCATION, "SEXP Container with empty name found");
|
|
log_printf(LOGFILE_EVENT_LOG, "SEXP Container with empty name found");
|
|
valid = false;
|
|
} else if (name.length() > sexp_container::NAME_MAX_LENGTH) {
|
|
Warning(LOCATION,
|
|
"SEXP Container name %s is longer than limit %d",
|
|
name.c_str(),
|
|
sexp_container::NAME_MAX_LENGTH);
|
|
log_printf(LOGFILE_EVENT_LOG,
|
|
"SEXP Container name %s is longer than limit %d",
|
|
name.c_str(),
|
|
sexp_container::NAME_MAX_LENGTH);
|
|
valid = false;
|
|
} else if (get_sexp_container(name.c_str()) != nullptr) {
|
|
Warning(LOCATION, "A SEXP Container named %s already exists", name.c_str());
|
|
log_printf(LOGFILE_EVENT_LOG, "A SEXP Container named %s already exists", name.c_str());
|
|
valid = false;
|
|
}
|
|
|
|
required_string("$Data Type:");
|
|
stuff_string(temp_type_string, F_NAME);
|
|
if (!stricmp(temp_type_string.c_str(), "Number")) {
|
|
type |= ContainerType::NUMBER_DATA;
|
|
opf_type = OPF_NUMBER;
|
|
} else if (!stricmp(temp_type_string.c_str(), "String")) {
|
|
type |= ContainerType::STRING_DATA;
|
|
opf_type = OPF_ANYTHING;
|
|
} else {
|
|
Warning(LOCATION, "Unknown SEXP Container type %s found", temp_type_string.c_str());
|
|
log_printf(LOGFILE_EVENT_LOG, "Unknown SEXP Container type %s found", temp_type_string.c_str());
|
|
valid = false;
|
|
}
|
|
|
|
if (optional_string("$Key Type:")) {
|
|
Assertion(any(type & ContainerType::MAP), "$Key Type: found for container which doesn't use keys!");
|
|
|
|
stuff_string(temp_type_string, F_NAME);
|
|
if (!stricmp(temp_type_string.c_str(), "Number")) {
|
|
type |= ContainerType::NUMBER_KEYS;
|
|
} else if (!stricmp(temp_type_string.c_str(), "String")) {
|
|
type |= ContainerType::STRING_KEYS;
|
|
} else {
|
|
Warning(LOCATION, "Unknown SEXP Container type %s found", temp_type_string.c_str());
|
|
log_printf(LOGFILE_EVENT_LOG, "Unknown SEXP Container type %s found", temp_type_string.c_str());
|
|
valid = false;
|
|
}
|
|
}
|
|
|
|
if (optional_string("+Strictly Typed Keys")) {
|
|
Assertion(any(type & ContainerType::MAP), "+Strictly Typed Keys found for container which doesn't use keys!");
|
|
Warning(LOCATION, "Container %s is marked for strictly typed keys, which are not yet supported.", name.c_str());
|
|
type |= ContainerType::STRICTLY_TYPED_KEYS;
|
|
}
|
|
|
|
if (optional_string("+Strictly Typed Data")) {
|
|
Warning(LOCATION, "Container %s is marked for strictly typed data, which are not yet supported.", name.c_str());
|
|
type |= ContainerType::STRICTLY_TYPED_DATA;
|
|
}
|
|
|
|
required_string("$Data:");
|
|
stuff_string_list(data);
|
|
|
|
if (optional_string("+Network Container")) {
|
|
Warning(LOCATION, "Container %s is marked as a network container, which is not yet supported.", name.c_str());
|
|
type |= ContainerType::NETWORK;
|
|
}
|
|
|
|
if (optional_string("+Eternal")) {
|
|
type |= ContainerType::SAVE_TO_PLAYER_FILE;
|
|
}
|
|
|
|
// campaign-persistent
|
|
if (optional_string("+Save On Mission Progress")) {
|
|
type |= ContainerType::SAVE_ON_MISSION_PROGRESS;
|
|
}
|
|
|
|
// player-persistent
|
|
if (optional_string("+Save On Mission Close")) {
|
|
Assert(none(type & ContainerType::SAVE_ON_MISSION_PROGRESS));
|
|
type |= ContainerType::SAVE_ON_MISSION_CLOSE;
|
|
}
|
|
|
|
Assert(none(type & ContainerType::SAVE_TO_PLAYER_FILE) ||
|
|
(any(type & ContainerType::SAVE_ON_MISSION_PROGRESS) ^ any(type & ContainerType::SAVE_ON_MISSION_CLOSE)));
|
|
|
|
return valid;
|
|
}
|
|
|
|
void stuff_sexp_list_containers()
|
|
{
|
|
SCP_vector<SCP_string> parsed_data;
|
|
|
|
while (required_string_either("$End Lists", "$Name:")) {
|
|
Sexp_containers.emplace_back();
|
|
auto &new_list = Sexp_containers.back();
|
|
|
|
new_list.type = ContainerType::LIST;
|
|
if (stuff_one_generic_sexp_container(new_list.container_name, new_list.type, new_list.opf_type, parsed_data)) {
|
|
std::copy(parsed_data.begin(), parsed_data.end(), back_inserter(new_list.list_data));
|
|
Containers_by_name_map.emplace(new_list.container_name, &new_list);
|
|
} else {
|
|
Sexp_containers.pop_back();
|
|
}
|
|
}
|
|
}
|
|
|
|
void stuff_sexp_map_containers()
|
|
{
|
|
SCP_vector<SCP_string> parsed_data;
|
|
|
|
while (required_string_either("$End Maps", "$Name:")) {
|
|
Sexp_containers.emplace_back();
|
|
auto& new_map = Sexp_containers.back();
|
|
|
|
new_map.type = ContainerType::MAP;
|
|
if (stuff_one_generic_sexp_container(new_map.container_name, new_map.type, new_map.opf_type, parsed_data)) {
|
|
if (parsed_data.size() % 2 != 0) {
|
|
Warning(LOCATION,
|
|
"Data in the SEXP Map container is corrupt. Must be an even number of entries. Instead have %d",
|
|
(int)parsed_data.size());
|
|
log_printf(LOGFILE_EVENT_LOG,
|
|
"Data in the SEXP Map container is corrupt. Must be an even number of entries. Instead have %d",
|
|
(int)parsed_data.size());
|
|
Sexp_containers.pop_back();
|
|
} else {
|
|
for (int i = 0; i < (int)parsed_data.size(); i += 2) {
|
|
new_map.map_data.emplace(parsed_data[i], parsed_data[i + 1]);
|
|
}
|
|
Containers_by_name_map.emplace(new_map.container_name, &new_map);
|
|
}
|
|
} else {
|
|
Sexp_containers.pop_back();
|
|
}
|
|
}
|
|
}
|
|
|
|
sexp_container *get_sexp_container(const char* name) {
|
|
auto container_it = Containers_by_name_map.find(name);
|
|
if (container_it != Containers_by_name_map.end()) {
|
|
return container_it->second;
|
|
} else {
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
static bool str_prefix(const char* prefix, const char* str)
|
|
{
|
|
for (; *prefix; ++prefix, ++str) {
|
|
if (SCP_tolower(*str) != SCP_tolower(*prefix)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Tests whether this position in a character array is the start of a container name
|
|
* Returns a pointer to the container or nullptr if not found
|
|
**/
|
|
sexp_container *get_sexp_container_special(const SCP_string& text, size_t start_pos)
|
|
{
|
|
Assert(!text.empty());
|
|
Assert(start_pos < text.length());
|
|
|
|
for (auto &container : Sexp_containers) {
|
|
if (str_prefix(container.container_name.c_str(), text.c_str())) {
|
|
return &container;
|
|
}
|
|
}
|
|
|
|
// not found
|
|
return nullptr;
|
|
}
|
|
|
|
// inspired by sexp_campaign_file_variable_count()
|
|
bool sexp_container_has_persistent_non_eternal_containers()
|
|
{
|
|
for (const auto &container : Sexp_containers) {
|
|
if (container.is_persistent() && !container.is_eternal()) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|