9#include <initializer_list>
11#include <BufferStream.h>
18using namespace std::string_view_literals;
23constexpr auto INVALID_SYNTAX_MSG =
"Invalid syntax found in FGD!";
24constexpr auto INVALID_CLASS_MSG =
"Invalid class found in FGD!";
26[[nodiscard]]
bool tryToEatSeparator(BufferStream& stream,
char sep) {
33[[nodiscard]] std::string_view readFGDString(BufferStreamReadOnly& stream, BufferStream& backing) {
36 static constexpr std::string_view END =
"\"\n";
38 const auto startSpan = backing.tell();
40 char c = stream.read<
char>();
42 stream.seek(-1, std::ios::cur);
44 if (stream.seek(-1, std::ios::cur).peek<
char>() !=
':') {
52 for (c = stream.read<
char>(); END.find(c) == std::string_view::npos; c = stream.read<
char>()) {
54 const auto n = stream.read<
char>();
57 }
else if (n ==
't') {
59 }
else if (n ==
'\"') {
61 }
else if (END.find(c) != std::string_view::npos) {
71 if (!::tryToEatSeparator(stream,
'+')) {
76 if (stream.peek<
char>() !=
'\"') {
81 if (stream.seek(-1, std::ios::cur).peek<
char>() !=
':') {
88 return {
reinterpret_cast<const char*
>(backing.data()) + startSpan,
static_cast<std::string_view::size_type
>(backing.tell() - 1 - startSpan)};
91void readVersion(BufferStreamReadOnly& stream, BufferStream& backing,
int& version) {
92 if (stream.seek(-1, std::ios::cur).peek<
char>() !=
'(') {
94 if (stream.peek<
char>() !=
'(') {
103void readMapSize(BufferStreamReadOnly& stream, BufferStream& backing, math::Vec2i& mapSize) {
104 if (stream.seek(-1, std::ios::cur).peek<
char>() !=
'(') {
106 if (stream.peek<
char>() !=
'(') {
112 if (mapSizes.size() != 2) {
122void readMaterialExclusionDirs(BufferStreamReadOnly& stream, BufferStream& backing, std::vector<std::string_view>& materialExclusionDirs) {
123 if (!::tryToEatSeparator(stream,
'[')) {
126 while (!::tryToEatSeparator(stream,
']')) {
127 materialExclusionDirs.push_back(::readFGDString(stream, backing));
132void readAutoVisGroups(BufferStreamReadOnly& stream, BufferStream& backing, std::vector<FGD::AutoVisGroup>& autoVisGroups) {
133 if (!::tryToEatSeparator(stream,
'=')) {
136 const auto parentName = ::readFGDString(stream, backing);
137 if (!::tryToEatSeparator(stream,
'[')) {
140 while (!::tryToEatSeparator(stream,
']')) {
141 auto& autoVisGroup = autoVisGroups.emplace_back();
142 autoVisGroup.parentName = parentName;
143 autoVisGroup.name = ::readFGDString(stream, backing);
144 if (!::tryToEatSeparator(stream,
'[')) {
147 while (!::tryToEatSeparator(stream,
']')) {
148 autoVisGroup.entities.push_back(::readFGDString(stream, backing));
155void readClassProperties(BufferStreamReadOnly& stream, BufferStream& backing,
FGD::Entity& entity) {
158 while (stream.peek<
char>() !=
'=') {
163 if (stream.seek(-1, std::ios::cur).peek<
char>() !=
'(') {
165 if (stream.peek<
char>() !=
'(') {
180void readEntityIO(BufferStreamReadOnly& stream, BufferStream& backing,
FGD::Entity& entity,
bool input) {
181 auto& io = input ? entity.
inputs.emplace_back() : entity.
outputs.emplace_back();
183 if (stream.seek(-1, std::ios::cur).peek<
char>() !=
'(') {
185 if (stream.peek<
char>() !=
'(') {
191 if (!::tryToEatSeparator(stream,
':')) {
195 io.description = ::readFGDString(stream, backing);
200void readEntityFieldModifiers(BufferStreamReadOnly& stream, BufferStream& backing,
bool& readonly,
bool& reportable) {
205 if (stream.peek<
char>() ==
'r') {
208 }
else if (modifier ==
"report") {
212 stream.seek(-
static_cast<int64_t
>(modifier.length()), std::ios::cur);
217 if (stream.peek<
char>() ==
'r') {
221 stream.seek(-
static_cast<int64_t
>(modifier.length()), std::ios::cur);
227void readEntityKeyValue(BufferStreamReadOnly& stream, BufferStream& backing,
FGD::Entity& entity) {
232 ::readEntityIO(stream, backing, entity,
true);
236 ::readEntityIO(stream, backing, entity,
false);
242 if (stream.peek<
char>() ==
')') {
249 ::readEntityFieldModifiers(stream, backing, field.readonly, field.reportable);
251 if (::tryToEatSeparator(stream,
':')) {
252 field.displayName = ::readFGDString(stream, backing);
256 if (::tryToEatSeparator(stream,
':')) {
257 field.valueDefault = ::readFGDString(stream, backing);
261 if (::tryToEatSeparator(stream,
':')) {
262 field.description = ::readFGDString(stream, backing);
266 if (!::tryToEatSeparator(stream,
'=') || !::tryToEatSeparator(stream,
'[')) {
269 while (stream.peek<
char>() !=
']') {
270 auto& choice = field.choices.emplace_back();
271 choice.value = ::readFGDString(stream, backing);
273 if (::tryToEatSeparator(stream,
':')) {
274 choice.displayName = ::readFGDString(stream, backing);
276 choice.displayName =
"";
283 ::readEntityFieldModifiers(stream, backing, field.readonly, field.reportable);
285 if (::tryToEatSeparator(stream,
':')) {
286 field.displayName = ::readFGDString(stream, backing);
290 if (::tryToEatSeparator(stream,
':')) {
291 field.description = ::readFGDString(stream, backing);
295 if (!::tryToEatSeparator(stream,
'=') || !::tryToEatSeparator(stream,
'[')) {
300 while (stream.peek<
char>() !=
']') {
301 auto& flag = field.flags.emplace_back();
302 const auto valueString = ::readFGDString(stream, backing);
303 if (
string::toInt(valueString, flag.value).ec != std::errc{}) {
307 if (!::tryToEatSeparator(stream,
':')) {
310 flag.displayName = ::readFGDString(stream, backing);
312 if (!::tryToEatSeparator(stream,
':')) {
315 const auto enabledByDefaultString = ::readFGDString(stream, backing);
316 int enabledByDefault = 0;
317 if (
string::toInt(enabledByDefaultString, enabledByDefault).ec != std::errc{}) {
318 flag.enabledByDefault =
false;
320 flag.enabledByDefault =
static_cast<bool>(enabledByDefault);
323 if (!::tryToEatSeparator(stream,
':')) {
326 flag.description = ::readFGDString(stream, backing);
330 auto& field = entity.
fields.emplace_back();
332 field.valueType = valueType;
333 ::readEntityFieldModifiers(stream, backing, field.readonly, field.reportable);
334 field.displayName =
"";
335 field.valueDefault =
"";
336 field.description =
"";
338 if (!::tryToEatSeparator(stream,
':')) {
341 field.displayName = ::readFGDString(stream, backing);
343 if (!::tryToEatSeparator(stream,
':')) {
346 field.valueDefault = ::readFGDString(stream, backing);
348 if (!::tryToEatSeparator(stream,
':')) {
351 field.description = ::readFGDString(stream, backing);
362 if (!newEntity.
docsURL.empty()) {
365 for (
const auto& field : newEntity.
fields) {
366 if (
auto it = std::ranges::find_if(oldEntity.
fields, [&field](
const auto& oldField) {
367 return oldField.name == field.name;
368 }); it != oldEntity.
fields.end()) {
369 it->valueType = field.valueType;
370 it->readonly = field.readonly;
371 it->reportable = field.reportable;
372 if (!field.displayName.empty()) {
373 it->displayName = field.displayName;
375 if (!field.valueDefault.empty()) {
376 it->valueDefault = field.valueDefault;
378 if (!field.description.empty()) {
379 it->description = field.description;
382 oldEntity.
fields.push_back(field);
386 if (
auto it = std::ranges::find_if(oldEntity.
fieldsWithChoices, [&field](
const auto& oldField) {
387 return oldField.name == field.name;
389 it->readonly = field.readonly;
390 it->reportable = field.reportable;
391 if (!field.displayName.empty()) {
392 it->displayName = field.displayName;
394 if (!field.valueDefault.empty()) {
395 it->valueDefault = field.valueDefault;
397 if (!field.description.empty()) {
398 it->description = field.description;
400 it->choices = field.choices;
406 if (
auto it = std::ranges::find_if(oldEntity.
fieldsWithFlags, [&field](
const auto& oldField) {
407 return oldField.name == field.name;
409 it->readonly = field.readonly;
410 it->reportable = field.reportable;
411 it->flags = field.flags;
416 for (
const auto& input : newEntity.
inputs) {
417 if (
auto it = std::ranges::find_if(oldEntity.
inputs, [&input](
const auto& oldInput) {
418 return oldInput.name == input.name;
419 }); it != oldEntity.
inputs.end()) {
420 it->valueType = input.valueType;
421 if (!input.description.empty()) {
422 it->description = input.description;
425 oldEntity.
inputs.push_back(input);
428 for (
const auto& output : newEntity.
outputs) {
429 if (
auto it = std::ranges::find_if(oldEntity.
outputs, [&output](
const auto& oldOutput) {
430 return oldOutput.name == output.name;
431 }); it != oldEntity.
outputs.end()) {
432 it->valueType = output.valueType;
433 if (!output.description.empty()) {
434 it->description = output.description;
437 oldEntity.
outputs.push_back(output);
442void readEntity(BufferStreamReadOnly& stream, BufferStream& backing, std::string_view classType, std::unordered_map<std::string_view, FGD::Entity>& entities,
bool extension) {
447 if (!::tryToEatSeparator(stream,
'=')) {
448 ::readClassProperties(stream, backing, entity);
453 const auto name = ::readFGDString(stream, backing);
456 if (::tryToEatSeparator(stream,
':')) {
457 entity.
description = ::readFGDString(stream, backing);
463 if (::tryToEatSeparator(stream,
':')) {
464 entity.
docsURL = ::readFGDString(stream, backing);
470 if (!::tryToEatSeparator(stream,
'[')) {
473 while (!::tryToEatSeparator(stream,
']')) {
474 ::readEntityKeyValue(stream, backing, entity);
478 if (extension && entities.contains(name)) {
480 ::overwriteEntity(entities[name], entity);
483 entities[name] = entity;
487void writeOptionalKeyValueStrings(BufferStream& writer, std::initializer_list<std::string_view> strings) {
488 static constexpr auto writeOptionalString = [](BufferStream& stream, std::string_view str) {
491 .write(
" : \""sv,
false)
495 stream.write(
" :"sv,
false);
498 for (
auto revString = std::rbegin(strings); revString != std::rend(strings); ++revString) {
499 if (revString->empty()) {
502 for (
auto string = std::begin(strings);
string != revString.base(); ++
string) {
503 writeOptionalString(writer, std::string{*
string});
517 if (fgdData.empty()) {
520 BufferStreamReadOnly stream{fgdData};
523 std::vector seenPaths{fgdPath};
525 }
catch (
const std::overflow_error&) {}
529 return this->version;
533 return this->mapSize;
537 return this->entities;
541 return this->materialExclusionDirs;
545 return this->autoVisGroups;
549void FGD::readEntities(BufferStreamReadOnly& stream,
const std::filesystem::path& path, std::vector<std::filesystem::path>& seenPaths) {
550 auto& backingString = this->
backingData.emplace_back();
552 backingString.resize(stream.size() * 2);
553 BufferStream backing{backingString,
false};
559 if (stream.read<
char>() !=
'@') {
568 if (std::ranges::find(seenPaths, fgdPath) != seenPaths.end()) {
571 seenPaths.push_back(fgdPath);
574 if (fgdData.empty()) {
578 BufferStreamReadOnly newStream{fgdData};
582 }
catch (
const std::overflow_error&) {}
584 ::readVersion(stream, backing, this->version);
586 ::readMapSize(stream, backing, this->mapSize);
588 ::readMaterialExclusionDirs(stream, backing, this->materialExclusionDirs);
590 ::readAutoVisGroups(stream, backing, this->autoVisGroups);
598 ::readEntity(stream, backing, classType, this->entities,
false);
600 ::readEntity(stream, backing, classType, this->entities,
true);
628 .write(
"@include \""sv,
false)
629 .write(fgdPath.string(),
false)
630 .write(
"\"\n\n"sv,
false);
635 this->writer.write(std::format(
"@version({})\n\n",
version),
false);
640 this->writer.write(std::format(
"@mapsize({}, {})\n\n",
mapSize[0],
mapSize[1]),
false);
649 this->writer.write(
"@MaterialExclusion\n[\n"sv,
false);
650 for (
const auto& dir : dirs) {
651 this->writer <<
'\t' <<
'\"';
652 this->writer.write(dir,
false);
653 this->writer <<
'\"' <<
'\n';
655 this->writer.write(
"]\n\n"sv,
false);
661 .write(
"@AutoVisGroup = \""sv,
false)
662 .write(parentName,
false)
663 .write(
"\"\n[\n"sv,
false);
668 return this->
visGroup(name, std::span{entities});
673 .write(
"\t\""sv,
false)
675 .write(
"\"\n\t[\n"sv,
false);
676 for (
const auto& entity : entities) {
678 .write(
"\t\t\""sv,
false)
679 .write(entity,
false)
680 .write(
"\"\n"sv,
false);
682 this->parent.writer.write(
"\t]\n"sv,
false);
687 this->parent.
writer.write(
"]\n\n"sv,
false);
692 return this->
beginEntity(classType, std::span{classProperties}, name, description, docsURL);
698 .write(classType,
false);
699 if (classProperties.empty()) {
702 this->writer <<
'\n';
703 for (
const auto& classProperty : classProperties) {
706 .write(classProperty,
false)
711 .write(
"= "sv,
false)
713 .write(
" :"sv,
false);
715 if (description.size() < 32) {
717 .write(
" \""sv,
false)
718 .write(description,
false);
721 .write(
"\n\t\""sv,
false)
722 .write(description,
false);
724 this->writer.write(
'\"');
725 if (!docsURL.empty()) {
727 .write(
" : \""sv,
false)
728 .write(docsURL,
false)
731 this->writer.write(
"\n[\n"sv,
false);
740 .write(valueType,
false)
743 this->parent.writer.write(
" readonly"sv,
false);
746 this->parent.writer.write(
" report"sv,
false);
748 ::writeOptionalKeyValueStrings(this->parent.writer, {displayName, valueDefault, description});
749 this->parent.writer <<
'\n';
757 .write(
"(choices)"sv,
false);
759 this->parent.writer.write(
" readonly"sv,
false);
762 this->parent.writer.write(
" report"sv,
false);
764 ::writeOptionalKeyValueStrings(this->parent.writer, {displayName, valueDefault, description});
765 this->parent.writer.write(
" =\n\t[\n"sv,
false);
770 this->parent.parent.writer
771 .write(
"\t\t\""sv,
false)
773 .write(
"\" : \""sv,
false)
774 .write(displayName,
false)
775 .write(
"\"\n"sv,
false);
780 this->parent.parent.
writer.write(
"\t]\n"sv,
false);
788 .write(
"(flags)"sv,
false);
790 this->parent.writer.write(
" readonly"sv,
false);
793 this->parent.writer.write(
" report"sv,
false);
795 ::writeOptionalKeyValueStrings(this->parent.writer, {displayName, description});
796 this->parent.writer.write(
" =\n\t[\n"sv,
false);
801 this->parent.parent.writer.write(std::format(
"\t\t{} : \"{}\" : {:b}", value, displayName, enabledByDefault),
false);
802 if (!description.empty()) {
803 this->parent.parent.writer.write(std::format(
" : \"{}\"", description),
false);
805 this->parent.parent.
writer.write(
'\n');
810 this->parent.parent.
writer.write(
"\t]\n"sv,
false);
817 .write(
"input "sv,
false)
820 .write(valueType,
false)
822 if (!description.empty()) {
824 .write(
" : \""sv,
false)
825 .write(description,
false)
828 this->parent.writer <<
'\n';
835 .write(
"output "sv,
false)
838 .write(valueType,
false)
840 if (!description.empty()) {
842 .write(
" : \""sv,
false)
843 .write(description,
false)
846 this->parent.writer <<
'\n';
851 this->parent.
writer.write(
"]\n\n"sv,
false);
857 std::string_view out{this->
backingData.data(),
static_cast<std::string_view::size_type
>(this->writer.tell())};
858 while (out.ends_with(
"\n\n")) {
859 out = out.substr(0, out.size() - 1);
861 return std::string{out};
std::string readFileText(const std::filesystem::path &filepath, std::size_t startOffset=0)
bool writeFileText(const std::filesystem::path &filepath, std::string_view text)
std::unordered_map< char, char > EscapeSequenceMap
void eatWhitespaceAndSingleLineComments(BufferStream &stream, std::string_view singleLineCommentStart=DEFAULT_SINGLE_LINE_COMMENT_START)
Eat all whitespace and single line comments after the current stream position.
void eatWhitespace(BufferStream &stream)
Eat all whitespace after the current stream position.
bool tryToEatChar(BufferStream &stream, char c)
If the given char exists at the current position, skip over it.
std::string_view readUnquotedStringToBuffer(BufferStream &stream, BufferStream &backing, const EscapeSequenceMap &escapeSequences=getDefaultEscapeSequences())
Read a string starting at the current stream position.
std::string_view readStringToBuffer(BufferStream &stream, BufferStream &backing, std::string_view start=DEFAULT_STRING_START, std::string_view end=DEFAULT_STRING_END, const EscapeSequenceMap &escapeSequences=getDefaultEscapeSequences())
Read a string starting at the current stream position.
std::vector< std::string > split(std::string_view s, char delim)
std::from_chars_result toInt(std::string_view number, std::integral auto &out, int base=10)
void trim(std::string &s)
bool iequals(std::string_view s1, std::string_view s2)