8#include <initializer_list>
10#include <BufferStream.h>
17using namespace std::string_view_literals;
22constexpr auto INVALID_SYNTAX_MSG =
"Invalid syntax found in FGD!";
23constexpr auto INVALID_CLASS_MSG =
"Invalid class found in FGD!";
25[[nodiscard]]
bool tryToEatSeparator(BufferStream& stream,
char sep) {
32[[nodiscard]] std::string_view readFGDString(BufferStreamReadOnly& stream, BufferStream& backing) {
35 static constexpr std::string_view END =
"\"\n";
37 const auto startSpan = backing.tell();
39 char c = stream.read<
char>();
41 stream.seek(-1, std::ios::cur);
43 if (stream.seek(-1, std::ios::cur).peek<
char>() !=
':') {
51 for (c = stream.read<
char>(); END.find(c) == std::string_view::npos; c = stream.read<
char>()) {
53 auto n = stream.read<
char>();
56 }
else if (END.find(n) != std::string_view::npos) {
66 if (!::tryToEatSeparator(stream,
'+')) {
71 if (stream.peek<
char>() !=
'\"') {
76 if (stream.seek(-1, std::ios::cur).peek<
char>() !=
':') {
83 return {
reinterpret_cast<const char*
>(backing.data()) + startSpan,
static_cast<std::string_view::size_type
>(backing.tell() - 1 - startSpan)};
86void readVersion(BufferStreamReadOnly& stream, BufferStream& backing,
int& version) {
87 if (stream.seek(-1, std::ios::cur).peek<
char>() !=
'(') {
89 if (stream.peek<
char>() !=
'(') {
98void readMapSize(BufferStreamReadOnly& stream, BufferStream& backing, math::Vec2i& mapSize) {
99 if (stream.seek(-1, std::ios::cur).peek<
char>() !=
'(') {
101 if (stream.peek<
char>() !=
'(') {
107 if (mapSizes.size() != 2) {
117void readMaterialExclusionDirs(BufferStreamReadOnly& stream, BufferStream& backing, std::vector<std::string_view>& materialExclusionDirs) {
118 if (!::tryToEatSeparator(stream,
'[')) {
121 while (!::tryToEatSeparator(stream,
']')) {
122 materialExclusionDirs.push_back(::readFGDString(stream, backing));
127void readAutoVisGroups(BufferStreamReadOnly& stream, BufferStream& backing, std::vector<FGD::AutoVisGroup>& autoVisGroups) {
128 if (!::tryToEatSeparator(stream,
'=')) {
131 const auto parentName = ::readFGDString(stream, backing);
132 if (!::tryToEatSeparator(stream,
'[')) {
135 while (!::tryToEatSeparator(stream,
']')) {
136 auto& autoVisGroup = autoVisGroups.emplace_back();
137 autoVisGroup.parentName = parentName;
138 autoVisGroup.name = ::readFGDString(stream, backing);
139 if (!::tryToEatSeparator(stream,
'[')) {
142 while (!::tryToEatSeparator(stream,
']')) {
143 autoVisGroup.entities.push_back(::readFGDString(stream, backing));
150void readClassProperties(BufferStreamReadOnly& stream, BufferStream& backing,
FGD::Entity& entity) {
153 while (stream.peek<
char>() !=
'=') {
158 if (stream.seek(-1, std::ios::cur).peek<
char>() !=
'(') {
160 if (stream.peek<
char>() !=
'(') {
175void readEntityIO(BufferStreamReadOnly& stream, BufferStream& backing,
FGD::Entity& entity,
bool input) {
176 auto& io = input ? entity.
inputs.emplace_back() : entity.
outputs.emplace_back();
178 if (stream.seek(-1, std::ios::cur).peek<
char>() !=
'(') {
180 if (stream.peek<
char>() !=
'(') {
186 if (!::tryToEatSeparator(stream,
':')) {
190 io.description = ::readFGDString(stream, backing);
195void readEntityFieldModifiers(BufferStreamReadOnly& stream, BufferStream& backing,
bool& readonly,
bool& reportable) {
200 if (stream.peek<
char>() ==
'r') {
203 }
else if (modifier ==
"report") {
207 stream.seek(-
static_cast<int64_t
>(modifier.length()), std::ios::cur);
212 if (stream.peek<
char>() ==
'r') {
216 stream.seek(-
static_cast<int64_t
>(modifier.length()), std::ios::cur);
222void readEntityKeyValue(BufferStreamReadOnly& stream, BufferStream& backing,
FGD::Entity& entity) {
227 ::readEntityIO(stream, backing, entity,
true);
231 ::readEntityIO(stream, backing, entity,
false);
237 if (stream.peek<
char>() ==
')') {
244 ::readEntityFieldModifiers(stream, backing, field.readonly, field.reportable);
246 if (::tryToEatSeparator(stream,
':')) {
247 field.displayName = ::readFGDString(stream, backing);
251 if (::tryToEatSeparator(stream,
':')) {
252 field.valueDefault = ::readFGDString(stream, backing);
256 if (::tryToEatSeparator(stream,
':')) {
257 field.description = ::readFGDString(stream, backing);
261 if (!::tryToEatSeparator(stream,
'=') || !::tryToEatSeparator(stream,
'[')) {
264 while (stream.peek<
char>() !=
']') {
265 auto& choice = field.choices.emplace_back();
266 choice.value = ::readFGDString(stream, backing);
268 if (::tryToEatSeparator(stream,
':')) {
269 choice.displayName = ::readFGDString(stream, backing);
271 choice.displayName =
"";
278 ::readEntityFieldModifiers(stream, backing, field.readonly, field.reportable);
280 if (::tryToEatSeparator(stream,
':')) {
281 field.displayName = ::readFGDString(stream, backing);
285 if (::tryToEatSeparator(stream,
':')) {
286 field.description = ::readFGDString(stream, backing);
290 if (!::tryToEatSeparator(stream,
'=') || !::tryToEatSeparator(stream,
'[')) {
295 while (stream.peek<
char>() !=
']') {
296 auto& flag = field.flags.emplace_back();
297 const auto valueString = ::readFGDString(stream, backing);
298 if (
string::toInt(valueString, flag.value).ec != std::errc{}) {
302 if (!::tryToEatSeparator(stream,
':')) {
305 flag.displayName = ::readFGDString(stream, backing);
307 if (!::tryToEatSeparator(stream,
':')) {
310 const auto enabledByDefaultString = ::readFGDString(stream, backing);
311 int enabledByDefault = 0;
312 if (
string::toInt(enabledByDefaultString, enabledByDefault).ec != std::errc{}) {
313 flag.enabledByDefault =
false;
315 flag.enabledByDefault =
static_cast<bool>(enabledByDefault);
318 if (!::tryToEatSeparator(stream,
':')) {
321 flag.description = ::readFGDString(stream, backing);
325 auto& field = entity.
fields.emplace_back();
327 field.valueType = valueType;
328 ::readEntityFieldModifiers(stream, backing, field.readonly, field.reportable);
329 field.displayName =
"";
330 field.valueDefault =
"";
331 field.description =
"";
333 if (!::tryToEatSeparator(stream,
':')) {
336 field.displayName = ::readFGDString(stream, backing);
338 if (!::tryToEatSeparator(stream,
':')) {
341 field.valueDefault = ::readFGDString(stream, backing);
343 if (!::tryToEatSeparator(stream,
':')) {
346 field.description = ::readFGDString(stream, backing);
357 if (!newEntity.
docsURL.empty()) {
360 for (
const auto& field : newEntity.
fields) {
361 if (
auto it = std::ranges::find_if(oldEntity.
fields, [&field](
const auto& oldField) {
362 return oldField.name == field.name;
363 }); it != oldEntity.
fields.end()) {
364 it->valueType = field.valueType;
365 it->readonly = field.readonly;
366 it->reportable = field.reportable;
367 if (!field.displayName.empty()) {
368 it->displayName = field.displayName;
370 if (!field.valueDefault.empty()) {
371 it->valueDefault = field.valueDefault;
373 if (!field.description.empty()) {
374 it->description = field.description;
377 oldEntity.
fields.push_back(field);
381 if (
auto it = std::ranges::find_if(oldEntity.
fieldsWithChoices, [&field](
const auto& oldField) {
382 return oldField.name == field.name;
384 it->readonly = field.readonly;
385 it->reportable = field.reportable;
386 if (!field.displayName.empty()) {
387 it->displayName = field.displayName;
389 if (!field.valueDefault.empty()) {
390 it->valueDefault = field.valueDefault;
392 if (!field.description.empty()) {
393 it->description = field.description;
395 it->choices = field.choices;
401 if (
auto it = std::ranges::find_if(oldEntity.
fieldsWithFlags, [&field](
const auto& oldField) {
402 return oldField.name == field.name;
404 it->readonly = field.readonly;
405 it->reportable = field.reportable;
406 it->flags = field.flags;
411 for (
const auto& input : newEntity.
inputs) {
412 if (
auto it = std::ranges::find_if(oldEntity.
inputs, [&input](
const auto& oldInput) {
413 return oldInput.name == input.name;
414 }); it != oldEntity.
inputs.end()) {
415 it->valueType = input.valueType;
416 if (!input.description.empty()) {
417 it->description = input.description;
420 oldEntity.
inputs.push_back(input);
423 for (
const auto& output : newEntity.
outputs) {
424 if (
auto it = std::ranges::find_if(oldEntity.
outputs, [&output](
const auto& oldOutput) {
425 return oldOutput.name == output.name;
426 }); it != oldEntity.
outputs.end()) {
427 it->valueType = output.valueType;
428 if (!output.description.empty()) {
429 it->description = output.description;
432 oldEntity.
outputs.push_back(output);
437void readEntity(BufferStreamReadOnly& stream, BufferStream& backing, std::string_view classType, std::unordered_map<std::string_view, FGD::Entity>& entities,
bool extension) {
442 if (!::tryToEatSeparator(stream,
'=')) {
443 ::readClassProperties(stream, backing, entity);
448 const auto name = ::readFGDString(stream, backing);
451 if (::tryToEatSeparator(stream,
':')) {
452 entity.
description = ::readFGDString(stream, backing);
458 if (::tryToEatSeparator(stream,
':')) {
459 entity.
docsURL = ::readFGDString(stream, backing);
465 if (!::tryToEatSeparator(stream,
'[')) {
468 while (!::tryToEatSeparator(stream,
']')) {
469 ::readEntityKeyValue(stream, backing, entity);
473 if (extension && entities.contains(name)) {
475 ::overwriteEntity(entities[name], entity);
478 entities[name] = entity;
482void writeOptionalKeyValueStrings(BufferStream& writer, std::initializer_list<std::string_view> strings) {
483 static constexpr auto writeOptionalString = [](BufferStream& stream, std::string_view str) {
486 .write(
" : \""sv,
false)
490 stream.write(
" :"sv,
false);
493 for (
auto revString = std::rbegin(strings); revString != std::rend(strings); ++revString) {
494 if (revString->empty()) {
497 for (
auto string = std::begin(strings);
string != revString.base(); ++
string) {
498 writeOptionalString(writer, std::string{*
string});
512 if (fgdData.empty()) {
515 BufferStreamReadOnly stream{fgdData};
518 std::vector seenPaths{fgdPath};
520 }
catch (
const std::overflow_error&) {}
524 return this->version;
528 return this->mapSize;
532 return this->entities;
536 return this->materialExclusionDirs;
540 return this->autoVisGroups;
544void FGD::readEntities(BufferStreamReadOnly& stream,
const std::filesystem::path& path, std::vector<std::filesystem::path>& seenPaths) {
545 auto& backingString = this->
backingData.emplace_back();
547 backingString.resize(stream.size() * 2);
548 BufferStream backing{backingString,
false};
554 if (stream.read<
char>() !=
'@') {
563 if (std::ranges::find(seenPaths, fgdPath) != seenPaths.end()) {
566 seenPaths.push_back(fgdPath);
569 if (fgdData.empty()) {
573 BufferStreamReadOnly newStream{fgdData};
577 }
catch (
const std::overflow_error&) {}
579 ::readVersion(stream, backing, this->version);
581 ::readMapSize(stream, backing, this->mapSize);
583 ::readMaterialExclusionDirs(stream, backing, this->materialExclusionDirs);
585 ::readAutoVisGroups(stream, backing, this->autoVisGroups);
593 ::readEntity(stream, backing, classType, this->entities,
false);
595 ::readEntity(stream, backing, classType, this->entities,
true);
623 .write(
"@include \""sv,
false)
624 .write(fgdPath.string(),
false)
625 .write(
"\"\n\n"sv,
false);
631 .write(
"@version("sv,
false)
632 .write(std::to_string(
version),
false)
633 .write(
")\n\n"sv,
false);
639 .write(
"@mapsize("sv,
false)
640 .write(std::to_string(
mapSize[0]),
false)
641 .write(
", "sv,
false)
642 .write(std::to_string(
mapSize[1]),
false)
643 .write(
")\n\n"sv,
false);
652 this->writer.write(
"@MaterialExclusion\n[\n"sv,
false);
653 for (
const auto& dir : dirs) {
654 this->writer <<
'\t' <<
'\"';
655 this->writer.write(dir,
false);
656 this->writer <<
'\"' <<
'\n';
658 this->writer.write(
"]\n\n"sv,
false);
664 .write(
"@AutoVisGroup = \""sv,
false)
665 .write(parentName,
false)
666 .write(
"\"\n[\n"sv,
false);
671 return this->
visGroup(name, std::span{entities});
676 .write(
"\t\""sv,
false)
678 .write(
"\"\n\t[\n"sv,
false);
679 for (
const auto& entity : entities) {
681 .write(
"\t\t\""sv,
false)
682 .write(entity,
false)
683 .write(
"\"\n"sv,
false);
685 this->parent.writer.write(
"\t]\n"sv,
false);
690 this->parent.
writer.write(
"]\n\n"sv,
false);
695 return this->
beginEntity(classType, std::span{classProperties}, name, description, docsURL);
701 .write(classType,
false);
702 if (classProperties.empty()) {
705 this->writer <<
'\n';
706 for (
const auto& classProperty : classProperties) {
709 .write(classProperty,
false)
714 .write(
"= "sv,
false)
716 .write(
" :"sv,
false);
718 if (description.size() < 32) {
720 .write(
" \""sv,
false)
721 .write(description,
false);
724 .write(
"\n\t\""sv,
false)
725 .write(description,
false);
727 this->writer.write(
'\"');
728 if (!docsURL.empty()) {
730 .write(
" : \""sv,
false)
731 .write(docsURL,
false)
734 this->writer.write(
"\n[\n"sv,
false);
743 .write(valueType,
false)
746 this->parent.writer.write(
" readonly"sv,
false);
749 this->parent.writer.write(
" report"sv,
false);
751 ::writeOptionalKeyValueStrings(this->parent.writer, {displayName, valueDefault, description});
752 this->parent.writer <<
'\n';
760 .write(
"(choices)"sv,
false);
762 this->parent.writer.write(
" readonly"sv,
false);
765 this->parent.writer.write(
" report"sv,
false);
767 ::writeOptionalKeyValueStrings(this->parent.writer, {displayName, valueDefault, description});
768 this->parent.writer.write(
" =\n\t[\n"sv,
false);
773 this->parent.parent.writer
774 .write(
"\t\t\""sv,
false)
776 .write(
"\" : \""sv,
false)
777 .write(displayName,
false)
778 .write(
"\"\n"sv,
false);
783 this->parent.parent.
writer.write(
"\t]\n"sv,
false);
791 .write(
"(flags)"sv,
false);
793 this->parent.writer.write(
" readonly"sv,
false);
796 this->parent.writer.write(
" report"sv,
false);
798 ::writeOptionalKeyValueStrings(this->parent.writer, {displayName, description});
799 this->parent.writer.write(
" =\n\t[\n"sv,
false);
804 this->parent.parent.writer
805 .write(
"\t\t"sv,
false)
806 .write(std::to_string(value),
false)
807 .write(
" : \""sv,
false)
808 .write(displayName,
false)
809 .write(
"\" : "sv,
false)
810 .write(std::to_string(enabledByDefault),
false);
811 if (!description.empty()) {
812 this->parent.parent.writer
813 .write(
" : \""sv,
false)
814 .write(description,
false)
817 this->parent.parent.
writer.write(
'\n');
822 this->parent.parent.
writer.write(
"\t]\n"sv,
false);
829 .write(
"input "sv,
false)
832 .write(valueType,
false)
834 if (!description.empty()) {
836 .write(
" : \""sv,
false)
837 .write(description,
false)
840 this->parent.writer <<
'\n';
847 .write(
"output "sv,
false)
850 .write(valueType,
false)
852 if (!description.empty()) {
854 .write(
" : \""sv,
false)
855 .write(description,
false)
858 this->parent.writer <<
'\n';
863 this->parent.
writer.write(
"]\n\n"sv,
false);
869 std::string_view out{this->
backingData.data(),
static_cast<std::string_view::size_type
>(this->writer.tell())};
870 while (out.ends_with(
"\n\n")) {
871 out = out.substr(0, out.size() - 1);
873 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)