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 readMainKeyValues(BufferStreamReadOnly& stream, BufferStream& backing, std::unordered_map<std::string_view, std::string_view>& mainKeyValues) {
133 if (!::tryToEatSeparator(stream,
'=')) {
136 if (!::tryToEatSeparator(stream,
'[')) {
139 while (!::tryToEatSeparator(stream,
']')) {
140 auto key = ::readFGDString(stream, backing);
141 if (!::tryToEatSeparator(stream,
':')) {
144 auto value = ::readFGDString(stream, backing);
145 mainKeyValues[key] = value;
150void readAutoVisGroups(BufferStreamReadOnly& stream, BufferStream& backing, std::vector<FGD::AutoVisGroup>& autoVisGroups) {
151 if (!::tryToEatSeparator(stream,
'=')) {
154 const auto parentName = ::readFGDString(stream, backing);
155 if (!::tryToEatSeparator(stream,
'[')) {
158 while (!::tryToEatSeparator(stream,
']')) {
159 auto& autoVisGroup = autoVisGroups.emplace_back();
160 autoVisGroup.parentName = parentName;
161 autoVisGroup.name = ::readFGDString(stream, backing);
162 if (!::tryToEatSeparator(stream,
'[')) {
165 while (!::tryToEatSeparator(stream,
']')) {
166 autoVisGroup.entities.push_back(::readFGDString(stream, backing));
173void readClassProperties(BufferStreamReadOnly& stream, BufferStream& backing,
FGD::Entity& entity) {
176 while (stream.peek<
char>() !=
'=') {
181 if (stream.seek(-1, std::ios::cur).peek<
char>() !=
'(') {
183 if (stream.peek<
char>() !=
'(') {
198void readEntityIO(BufferStreamReadOnly& stream, BufferStream& backing,
FGD::Entity& entity,
bool input) {
199 auto& io = input ? entity.
inputs.emplace_back() : entity.
outputs.emplace_back();
201 if (stream.seek(-1, std::ios::cur).peek<
char>() !=
'(') {
203 if (stream.peek<
char>() !=
'(') {
209 if (!::tryToEatSeparator(stream,
':')) {
213 io.description = ::readFGDString(stream, backing);
218void readEntityFieldModifiers(BufferStreamReadOnly& stream, BufferStream& backing,
bool& readonly,
bool& reportable) {
223 if (stream.peek<
char>() ==
'r') {
226 }
else if (modifier ==
"report") {
230 stream.seek(-
static_cast<int64_t
>(modifier.length()), std::ios::cur);
235 if (stream.peek<
char>() ==
'r') {
239 stream.seek(-
static_cast<int64_t
>(modifier.length()), std::ios::cur);
245void readEntityKeyValue(BufferStreamReadOnly& stream, BufferStream& backing,
FGD::Entity& entity) {
250 ::readEntityIO(stream, backing, entity,
true);
254 ::readEntityIO(stream, backing, entity,
false);
260 if (stream.peek<
char>() ==
')') {
267 ::readEntityFieldModifiers(stream, backing, field.readonly, field.reportable);
269 if (::tryToEatSeparator(stream,
':')) {
270 field.displayName = ::readFGDString(stream, backing);
274 if (::tryToEatSeparator(stream,
':')) {
275 field.valueDefault = ::readFGDString(stream, backing);
279 if (::tryToEatSeparator(stream,
':')) {
280 field.description = ::readFGDString(stream, backing);
284 if (!::tryToEatSeparator(stream,
'=') || !::tryToEatSeparator(stream,
'[')) {
287 while (stream.peek<
char>() !=
']') {
288 auto& choice = field.choices.emplace_back();
289 choice.value = ::readFGDString(stream, backing);
291 if (::tryToEatSeparator(stream,
':')) {
292 choice.displayName = ::readFGDString(stream, backing);
294 choice.displayName =
"";
301 ::readEntityFieldModifiers(stream, backing, field.readonly, field.reportable);
303 if (::tryToEatSeparator(stream,
':')) {
304 field.displayName = ::readFGDString(stream, backing);
308 if (::tryToEatSeparator(stream,
':')) {
309 field.description = ::readFGDString(stream, backing);
313 if (!::tryToEatSeparator(stream,
'=') || !::tryToEatSeparator(stream,
'[')) {
318 while (stream.peek<
char>() !=
']') {
319 auto& flag = field.flags.emplace_back();
320 const auto valueString = ::readFGDString(stream, backing);
321 if (
string::toInt(valueString, flag.value).ec != std::errc{}) {
325 if (!::tryToEatSeparator(stream,
':')) {
328 flag.displayName = ::readFGDString(stream, backing);
330 if (!::tryToEatSeparator(stream,
':')) {
333 const auto enabledByDefaultString = ::readFGDString(stream, backing);
334 int enabledByDefault = 0;
335 if (
string::toInt(enabledByDefaultString, enabledByDefault).ec != std::errc{}) {
336 flag.enabledByDefault =
false;
338 flag.enabledByDefault =
static_cast<bool>(enabledByDefault);
341 if (!::tryToEatSeparator(stream,
':')) {
344 flag.description = ::readFGDString(stream, backing);
348 auto& field = entity.
fields.emplace_back();
350 field.valueType = valueType;
351 ::readEntityFieldModifiers(stream, backing, field.readonly, field.reportable);
352 field.displayName =
"";
353 field.valueDefault =
"";
354 field.description =
"";
356 if (!::tryToEatSeparator(stream,
':')) {
359 field.displayName = ::readFGDString(stream, backing);
361 if (!::tryToEatSeparator(stream,
':')) {
364 field.valueDefault = ::readFGDString(stream, backing);
366 if (!::tryToEatSeparator(stream,
':')) {
369 field.description = ::readFGDString(stream, backing);
380 if (!newEntity.
docsURL.empty()) {
383 for (
const auto& field : newEntity.
fields) {
384 if (
auto it = std::ranges::find_if(oldEntity.
fields, [&field](
const auto& oldField) {
385 return oldField.name == field.name;
386 }); it != oldEntity.
fields.end()) {
387 it->valueType = field.valueType;
388 it->readonly = field.readonly;
389 it->reportable = field.reportable;
390 if (!field.displayName.empty()) {
391 it->displayName = field.displayName;
393 if (!field.valueDefault.empty()) {
394 it->valueDefault = field.valueDefault;
396 if (!field.description.empty()) {
397 it->description = field.description;
400 oldEntity.
fields.push_back(field);
404 if (
auto it = std::ranges::find_if(oldEntity.
fieldsWithChoices, [&field](
const auto& oldField) {
405 return oldField.name == field.name;
407 it->readonly = field.readonly;
408 it->reportable = field.reportable;
409 if (!field.displayName.empty()) {
410 it->displayName = field.displayName;
412 if (!field.valueDefault.empty()) {
413 it->valueDefault = field.valueDefault;
415 if (!field.description.empty()) {
416 it->description = field.description;
418 it->choices = field.choices;
424 if (
auto it = std::ranges::find_if(oldEntity.
fieldsWithFlags, [&field](
const auto& oldField) {
425 return oldField.name == field.name;
427 it->readonly = field.readonly;
428 it->reportable = field.reportable;
429 it->flags = field.flags;
434 for (
const auto& input : newEntity.
inputs) {
435 if (
auto it = std::ranges::find_if(oldEntity.
inputs, [&input](
const auto& oldInput) {
436 return oldInput.name == input.name;
437 }); it != oldEntity.
inputs.end()) {
438 it->valueType = input.valueType;
439 if (!input.description.empty()) {
440 it->description = input.description;
443 oldEntity.
inputs.push_back(input);
446 for (
const auto& output : newEntity.
outputs) {
447 if (
auto it = std::ranges::find_if(oldEntity.
outputs, [&output](
const auto& oldOutput) {
448 return oldOutput.name == output.name;
449 }); it != oldEntity.
outputs.end()) {
450 it->valueType = output.valueType;
451 if (!output.description.empty()) {
452 it->description = output.description;
455 oldEntity.
outputs.push_back(output);
460void readEntity(BufferStreamReadOnly& stream, BufferStream& backing, std::string_view classType, std::unordered_map<std::string_view, FGD::Entity>& entities,
bool extension) {
465 if (!::tryToEatSeparator(stream,
'=')) {
466 ::readClassProperties(stream, backing, entity);
471 const auto name = ::readFGDString(stream, backing);
474 if (::tryToEatSeparator(stream,
':')) {
475 entity.
description = ::readFGDString(stream, backing);
481 if (::tryToEatSeparator(stream,
':')) {
482 entity.
docsURL = ::readFGDString(stream, backing);
488 if (!::tryToEatSeparator(stream,
'[')) {
491 while (!::tryToEatSeparator(stream,
']')) {
492 ::readEntityKeyValue(stream, backing, entity);
496 if (extension && entities.contains(name)) {
498 ::overwriteEntity(entities[name], entity);
501 entities[name] = entity;
505void writeOptionalKeyValueStrings(BufferStream& writer, std::initializer_list<std::string_view> strings) {
506 static constexpr auto writeOptionalString = [](BufferStream& stream, std::string_view str) {
509 .write(
" : \""sv,
false)
513 stream.write(
" :"sv,
false);
516 for (
auto revString = std::rbegin(strings); revString != std::rend(strings); ++revString) {
517 if (revString->empty()) {
520 for (
auto string = std::begin(strings);
string != revString.base(); ++
string) {
521 writeOptionalString(writer, std::string{*
string});
535 if (fgdData.empty()) {
538 BufferStreamReadOnly stream{fgdData};
541 std::vector seenPaths{fgdPath};
543 }
catch (
const std::overflow_error&) {}
547 return this->version;
551 return this->mapSize;
555 return this->entities;
559 return this->materialExclusionDirs;
563 return this->mainKeyValues;
567 return this->autoVisGroups;
571void FGD::readEntities(BufferStreamReadOnly& stream,
const std::filesystem::path& path, std::vector<std::filesystem::path>& seenPaths) {
572 auto& backingString = this->
backingData.emplace_back();
574 backingString.resize(stream.size() * 2);
575 BufferStream backing{backingString,
false};
581 if (stream.read<
char>() !=
'@') {
590 if (std::ranges::find(seenPaths, fgdPath) != seenPaths.end()) {
593 seenPaths.push_back(fgdPath);
596 if (fgdData.empty()) {
600 BufferStreamReadOnly newStream{fgdData};
604 }
catch (
const std::overflow_error&) {}
606 ::readVersion(stream, backing, this->version);
608 ::readMapSize(stream, backing, this->mapSize);
610 ::readMaterialExclusionDirs(stream, backing, this->materialExclusionDirs);
612 ::readMainKeyValues(stream, backing, this->mainKeyValues);
614 ::readAutoVisGroups(stream, backing, this->autoVisGroups);
622 ::readEntity(stream, backing, classType, this->entities,
false);
624 ::readEntity(stream, backing, classType, this->entities,
true);
652 .write(
"@include \""sv,
false)
653 .write(fgdPath.string(),
false)
654 .write(
"\"\n\n"sv,
false);
659 this->writer.write(std::format(
"@version({})\n\n",
version),
false);
664 this->writer.write(std::format(
"@mapsize({}, {})\n\n",
mapSize[0],
mapSize[1]),
false);
669 return this->materialExclusionDirs(std::span{dirs});
673 this->writer.write(
"@MaterialExclusion\n[\n"sv,
false);
674 for (
const auto& dir : dirs) {
675 this->writer <<
'\t' <<
'\"';
676 this->writer.write(dir,
false);
677 this->writer <<
'\"' <<
'\n';
679 this->writer.write(
"]\n\n"sv,
false);
684 this->writer.write(
"@Main =\n[\n"sv,
false);
689 this->writer <<
'\t';
690 this->writer.write(key,
false);
691 this->writer <<
':' <<
' ' <<
'\"';
692 this->writer.write(value,
false);
693 this->writer <<
'\"' <<
'\n';
698 this->writer.write(
"]\n\n"sv,
false);
704 .write(
"@AutoVisGroup = \""sv,
false)
705 .write(parentName,
false)
706 .write(
"\"\n[\n"sv,
false);
711 return this->
visGroup(name, std::span{entities});
716 .write(
"\t\""sv,
false)
718 .write(
"\"\n\t[\n"sv,
false);
719 for (
const auto& entity : entities) {
721 .write(
"\t\t\""sv,
false)
722 .write(entity,
false)
723 .write(
"\"\n"sv,
false);
725 this->parent.writer.write(
"\t]\n"sv,
false);
730 this->parent.
writer.write(
"]\n\n"sv,
false);
735 return this->
beginEntity(classType, std::span{classProperties}, name, description, docsURL);
741 .write(classType,
false);
742 if (classProperties.empty()) {
745 this->writer <<
'\n';
746 for (
const auto& classProperty : classProperties) {
749 .write(classProperty,
false)
754 .write(
"= "sv,
false)
756 .write(
" :"sv,
false);
758 if (description.size() < 32) {
760 .write(
" \""sv,
false)
761 .write(description,
false);
764 .write(
"\n\t\""sv,
false)
765 .write(description,
false);
767 this->writer.write(
'\"');
768 if (!docsURL.empty()) {
770 .write(
" : \""sv,
false)
771 .write(docsURL,
false)
774 this->writer.write(
"\n[\n"sv,
false);
783 .write(valueType,
false)
786 this->parent.writer.write(
" readonly"sv,
false);
789 this->parent.writer.write(
" report"sv,
false);
791 ::writeOptionalKeyValueStrings(this->parent.writer, {displayName, valueDefault, description});
792 this->parent.writer <<
'\n';
800 .write(
"(choices)"sv,
false);
802 this->parent.writer.write(
" readonly"sv,
false);
805 this->parent.writer.write(
" report"sv,
false);
807 ::writeOptionalKeyValueStrings(this->parent.writer, {displayName, valueDefault, description});
808 this->parent.writer.write(
" =\n\t[\n"sv,
false);
813 this->parent.parent.writer
814 .write(
"\t\t\""sv,
false)
816 .write(
"\" : \""sv,
false)
817 .write(displayName,
false)
818 .write(
"\"\n"sv,
false);
823 this->parent.parent.
writer.write(
"\t]\n"sv,
false);
831 .write(
"(flags)"sv,
false);
833 this->parent.writer.write(
" readonly"sv,
false);
836 this->parent.writer.write(
" report"sv,
false);
838 ::writeOptionalKeyValueStrings(this->parent.writer, {displayName, description});
839 this->parent.writer.write(
" =\n\t[\n"sv,
false);
844 this->parent.parent.writer.write(std::format(
"\t\t{} : \"{}\" : {:b}", value, displayName, enabledByDefault),
false);
845 if (!description.empty()) {
846 this->parent.parent.writer.write(std::format(
" : \"{}\"", description),
false);
848 this->parent.parent.
writer.write(
'\n');
853 this->parent.parent.
writer.write(
"\t]\n"sv,
false);
860 .write(
"input "sv,
false)
863 .write(valueType,
false)
865 if (!description.empty()) {
867 .write(
" : \""sv,
false)
868 .write(description,
false)
871 this->parent.writer <<
'\n';
878 .write(
"output "sv,
false)
881 .write(valueType,
false)
883 if (!description.empty()) {
885 .write(
" : \""sv,
false)
886 .write(description,
false)
889 this->parent.writer <<
'\n';
894 this->parent.
writer.write(
"]\n\n"sv,
false);
900 std::string_view out{this->
backingData.data(),
static_cast<std::string_view::size_type
>(this->writer.tell())};
901 while (out.ends_with(
"\n\n")) {
902 out = out.substr(0, out.size() - 1);
904 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)