5#include <initializer_list>
7#include <BufferStream.h>
15using namespace std::string_view_literals;
20constexpr auto INVALID_SYNTAX_MSG =
"Invalid syntax found in FGD!";
21constexpr auto INVALID_CLASS_MSG =
"Invalid class found in FGD!";
23[[nodiscard]]
bool tryToEatSeparator(BufferStream& stream,
char sep) {
30[[nodiscard]] std::string_view readFGDString(BufferStreamReadOnly& stream, BufferStream& backing) {
33 static constexpr std::string_view END =
"\"\n";
35 const auto startSpan = backing.tell();
37 char c = stream.read<
char>();
39 stream.seek(-1, std::ios::cur);
41 if (stream.seek(-1, std::ios::cur).peek<
char>() !=
':') {
48 for (c = stream.read<
char>(); END.find(c) == std::string_view::npos; c = stream.read<
char>()) {
50 auto n = stream.read<
char>();
53 }
else if (END.find(n) != std::string_view::npos) {
63 if (!::tryToEatSeparator(stream,
'+')) {
68 if (stream.peek<
char>() !=
'\"') {
73 if (stream.seek(-1, std::ios::cur).peek<
char>() !=
':') {
79 return {
reinterpret_cast<const char*
>(backing.data()) + startSpan, backing.tell() - 1 - startSpan};
82void readVersion(BufferStreamReadOnly& stream, BufferStream& backing,
int& version) {
83 if (stream.seek(-1, std::ios::cur).peek<
char>() !=
'(') {
85 if (stream.peek<
char>() !=
'(') {
94void readMapSize(BufferStreamReadOnly& stream, BufferStream& backing, math::Vec2i& mapSize) {
95 if (stream.seek(-1, std::ios::cur).peek<
char>() !=
'(') {
97 if (stream.peek<
char>() !=
'(') {
103 if (mapSizes.size() != 2) {
113void readMaterialExclusionDirs(BufferStreamReadOnly& stream, BufferStream& backing, std::vector<std::string_view>& materialExclusionDirs) {
114 if (!::tryToEatSeparator(stream,
'[')) {
117 while (!::tryToEatSeparator(stream,
']')) {
118 materialExclusionDirs.push_back(::readFGDString(stream, backing));
123void readAutoVisGroups(BufferStreamReadOnly& stream, BufferStream& backing, std::vector<FGD::AutoVisGroup>& autoVisGroups) {
124 if (!::tryToEatSeparator(stream,
'=')) {
127 const auto parentName = ::readFGDString(stream, backing);
128 if (!::tryToEatSeparator(stream,
'[')) {
131 while (!::tryToEatSeparator(stream,
']')) {
132 auto& autoVisGroup = autoVisGroups.emplace_back();
133 autoVisGroup.parentName = parentName;
134 autoVisGroup.name = ::readFGDString(stream, backing);
135 if (!::tryToEatSeparator(stream,
'[')) {
138 while (!::tryToEatSeparator(stream,
']')) {
139 autoVisGroup.entities.push_back(::readFGDString(stream, backing));
146void readClassProperties(BufferStreamReadOnly& stream, BufferStream& backing,
FGD::Entity& entity) {
149 while (stream.peek<
char>() !=
'=') {
154 if (stream.seek(-1, std::ios::cur).peek<
char>() !=
'(') {
156 if (stream.peek<
char>() !=
'(') {
171void readEntityIO(BufferStreamReadOnly& stream, BufferStream& backing,
FGD::Entity& entity,
bool input) {
172 auto& io = input ? entity.
inputs.emplace_back() : entity.
outputs.emplace_back();
174 if (stream.seek(-1, std::ios::cur).peek<
char>() !=
'(') {
176 if (stream.peek<
char>() !=
'(') {
182 if (!::tryToEatSeparator(stream,
':')) {
186 io.description = ::readFGDString(stream, backing);
191void readEntityFieldModifiers(BufferStreamReadOnly& stream, BufferStream& backing,
bool& readonly,
bool& reportable) {
196 if (stream.peek<
char>() ==
'r') {
199 }
else if (modifier ==
"report") {
203 stream.seek(-
static_cast<int64_t
>(modifier.length()), std::ios::cur);
208 if (stream.peek<
char>() ==
'r') {
212 stream.seek(-
static_cast<int64_t
>(modifier.length()), std::ios::cur);
218void readEntityKeyValue(BufferStreamReadOnly& stream, BufferStream& backing,
FGD::Entity& entity) {
223 ::readEntityIO(stream, backing, entity,
true);
227 ::readEntityIO(stream, backing, entity,
false);
233 if (stream.peek<
char>() ==
')') {
240 ::readEntityFieldModifiers(stream, backing, field.readonly, field.reportable);
242 if (::tryToEatSeparator(stream,
':')) {
243 field.displayName = ::readFGDString(stream, backing);
247 if (::tryToEatSeparator(stream,
':')) {
248 field.valueDefault = ::readFGDString(stream, backing);
252 if (::tryToEatSeparator(stream,
':')) {
253 field.description = ::readFGDString(stream, backing);
257 if (!::tryToEatSeparator(stream,
'=') || !::tryToEatSeparator(stream,
'[')) {
260 while (stream.peek<
char>() !=
']') {
261 auto& choice = field.choices.emplace_back();
262 choice.value = ::readFGDString(stream, backing);
264 if (::tryToEatSeparator(stream,
':')) {
265 choice.displayName = ::readFGDString(stream, backing);
267 choice.displayName =
"";
274 ::readEntityFieldModifiers(stream, backing, field.readonly, field.reportable);
276 if (::tryToEatSeparator(stream,
':')) {
277 field.displayName = ::readFGDString(stream, backing);
281 if (::tryToEatSeparator(stream,
':')) {
282 field.description = ::readFGDString(stream, backing);
286 if (!::tryToEatSeparator(stream,
'=') || !::tryToEatSeparator(stream,
'[')) {
291 while (stream.peek<
char>() !=
']') {
292 auto& flag = field.flags.emplace_back();
293 const auto valueString = ::readFGDString(stream, backing);
294 if (
string::toInt(valueString, flag.value).ec != std::errc{}) {
298 if (!::tryToEatSeparator(stream,
':')) {
301 flag.displayName = ::readFGDString(stream, backing);
303 if (!::tryToEatSeparator(stream,
':')) {
306 const auto enabledByDefaultString = ::readFGDString(stream, backing);
307 int enabledByDefault = 0;
308 if (
string::toInt(enabledByDefaultString, enabledByDefault).ec != std::errc{}) {
309 flag.enabledByDefault =
false;
311 flag.enabledByDefault =
static_cast<bool>(enabledByDefault);
314 if (!::tryToEatSeparator(stream,
':')) {
317 flag.description = ::readFGDString(stream, backing);
321 auto& field = entity.
fields.emplace_back();
323 field.valueType = valueType;
324 ::readEntityFieldModifiers(stream, backing, field.readonly, field.reportable);
325 field.displayName =
"";
326 field.valueDefault =
"";
327 field.description =
"";
329 if (!::tryToEatSeparator(stream,
':')) {
332 field.displayName = ::readFGDString(stream, backing);
334 if (!::tryToEatSeparator(stream,
':')) {
337 field.valueDefault = ::readFGDString(stream, backing);
339 if (!::tryToEatSeparator(stream,
':')) {
342 field.description = ::readFGDString(stream, backing);
353 if (!newEntity.
docsURL.empty()) {
356 for (
const auto& field : newEntity.
fields) {
357 if (
auto it = std::find_if(oldEntity.
fields.begin(), oldEntity.
fields.end(), [&field](
const auto& oldField) {
358 return oldField.name == field.name;
359 }); it != oldEntity.
fields.end()) {
360 it->valueType = field.valueType;
361 it->readonly = field.readonly;
362 it->reportable = field.reportable;
363 if (!field.displayName.empty()) {
364 it->displayName = field.displayName;
366 if (!field.valueDefault.empty()) {
367 it->valueDefault = field.valueDefault;
369 if (!field.description.empty()) {
370 it->description = field.description;
373 oldEntity.
fields.push_back(field);
378 return oldField.name == field.name;
380 it->readonly = field.readonly;
381 it->reportable = field.reportable;
382 if (!field.displayName.empty()) {
383 it->displayName = field.displayName;
385 if (!field.valueDefault.empty()) {
386 it->valueDefault = field.valueDefault;
388 if (!field.description.empty()) {
389 it->description = field.description;
391 it->choices = field.choices;
398 return oldField.name == field.name;
400 it->readonly = field.readonly;
401 it->reportable = field.reportable;
402 it->flags = field.flags;
407 for (
const auto& input : newEntity.
inputs) {
408 if (
auto it = std::find_if(oldEntity.
inputs.begin(), oldEntity.
inputs.end(), [&input](
const auto& oldInput) {
409 return oldInput.name == input.name;
410 }); it != oldEntity.
inputs.end()) {
411 it->valueType = input.valueType;
412 if (!input.description.empty()) {
413 it->description = input.description;
416 oldEntity.
inputs.push_back(input);
419 for (
const auto& output : newEntity.
outputs) {
420 if (
auto it = std::find_if(oldEntity.
outputs.begin(), oldEntity.
outputs.end(), [&output](
const auto& oldOutput) {
421 return oldOutput.name == output.name;
422 }); it != oldEntity.
outputs.end()) {
423 it->valueType = output.valueType;
424 if (!output.description.empty()) {
425 it->description = output.description;
428 oldEntity.
outputs.push_back(output);
433void readEntity(BufferStreamReadOnly& stream, BufferStream& backing, std::string_view classType, std::unordered_map<std::string_view, FGD::Entity>& entities,
bool extension) {
438 if (!::tryToEatSeparator(stream,
'=')) {
439 ::readClassProperties(stream, backing, entity);
444 const auto name = ::readFGDString(stream, backing);
447 if (::tryToEatSeparator(stream,
':')) {
448 entity.
description = ::readFGDString(stream, backing);
454 if (::tryToEatSeparator(stream,
':')) {
455 entity.
docsURL = ::readFGDString(stream, backing);
461 if (!::tryToEatSeparator(stream,
'[')) {
464 while (!::tryToEatSeparator(stream,
']')) {
465 ::readEntityKeyValue(stream, backing, entity);
469 if (extension && entities.contains(name)) {
471 ::overwriteEntity(entities[name], entity);
474 entities[name] = entity;
478void writeOptionalKeyValueStrings(BufferStream& writer, std::initializer_list<std::string_view> strings) {
479 static constexpr auto writeOptionalString = [](BufferStream& stream,
const std::string& str) {
482 .write(
" : \""sv,
false)
486 stream.write(
" :"sv,
false);
489 for (
auto revString = std::rbegin(strings); revString != std::rend(strings); ++revString) {
490 if (revString->empty()) {
493 for (
auto string = std::begin(strings);
string != revString.base(); ++string) {
494 writeOptionalString(writer, std::string{*
string});
508 if (fgdData.empty()) {
511 BufferStreamReadOnly stream{fgdData};
514 std::vector seenPaths{fgdPath};
517 }
catch (
const std::overflow_error&) {}
541void FGD::readEntities(BufferStreamReadOnly& stream,
const std::string& path, std::vector<std::string>& seenPaths) {
542 auto& backingString = this->
backingData.emplace_back();
544 backingString.resize(stream.size() * 2);
545 BufferStream backing{backingString,
false};
551 if (stream.read<
char>() !=
'@') {
561 if (std::find(seenPaths.begin(), seenPaths.end(), fgdPath) != seenPaths.end()) {
564 seenPaths.push_back(fgdPath);
567 if (fgdData.empty()) {
571 BufferStreamReadOnly newStream{fgdData};
574 }
catch (
const std::overflow_error&) {}
576 ::readVersion(stream, backing, this->version);
578 ::readMapSize(stream, backing, this->mapSize);
580 ::readMaterialExclusionDirs(stream, backing, this->materialExclusionDirs);
582 ::readAutoVisGroups(stream, backing, this->autoVisGroups);
590 ::readEntity(stream, backing, classType, this->entities,
false);
592 ::readEntity(stream, backing, classType, this->entities,
true);
620 .write(
"@include \""sv,
false)
621 .write(fgdPath,
false)
622 .write(
"\"\n\n"sv,
false);
628 .write(
"@version("sv,
false)
629 .write(std::to_string(
version),
false)
630 .write(
")\n\n"sv,
false);
636 .write(
"@mapsize("sv,
false)
637 .write(std::to_string(
mapSize[0]),
false)
638 .write(
", "sv,
false)
639 .write(std::to_string(
mapSize[1]),
false)
640 .write(
")\n\n"sv,
false);
645 this->writer.write(
"@MaterialExclusion\n[\n"sv,
false);
646 for (
const auto& dir : dirs) {
647 this->writer <<
'\t' <<
'\"';
648 this->writer.write(dir,
false);
649 this->writer <<
'\"' <<
'\n';
651 this->writer.write(
"]\n\n"sv,
false);
657 .write(
"@AutoVisGroup = \""sv,
false)
658 .write(parentName,
false)
659 .write(
"\"\n[\n"sv,
false);
665 .write(
"\t\""sv,
false)
667 .write(
"\"\n\t[\n"sv,
false);
668 for (
const auto& entity : entities) {
670 .write(
"\t\t\""sv,
false)
671 .write(entity,
false)
672 .write(
"\"\n"sv,
false);
674 this->parent.
writer.write(
"\t]\n"sv,
false);
679 this->parent.
writer.write(
"]\n\n"sv,
false);
686 .write(classType,
false);
687 if (classProperties.empty()) {
690 this->writer <<
'\n';
691 for (
const auto& classProperty : classProperties) {
694 .write(classProperty,
false)
699 .write(
"= "sv,
false)
701 .write(
" :"sv,
false);
703 if (description.size() < 32) {
705 .write(
" \""sv,
false)
706 .write(description,
false);
709 .write(
"\n\t\""sv,
false)
710 .write(description,
false);
712 this->writer.write(
'\"');
713 if (!docsURL.empty()) {
715 .write(
" : \""sv,
false)
716 .write(docsURL,
false)
719 this->writer.write(
"\n[\n"sv,
false);
728 .write(valueType,
false)
731 this->parent.writer.write(
" readonly"sv,
false);
734 this->parent.writer.write(
" report"sv,
false);
736 ::writeOptionalKeyValueStrings(this->parent.writer, {displayName, valueDefault, description});
737 this->parent.writer <<
'\n';
745 .write(
"(choices)"sv,
false);
747 this->parent.writer.write(
" readonly"sv,
false);
750 this->parent.writer.write(
" report"sv,
false);
752 ::writeOptionalKeyValueStrings(this->parent.writer, {displayName, valueDefault, description});
753 this->parent.writer.write(
" =\n\t[\n"sv,
false);
758 this->parent.parent.writer
759 .write(
"\t\t\""sv,
false)
761 .write(
"\" : \""sv,
false)
762 .write(displayName,
false)
763 .write(
"\"\n"sv,
false);
768 this->parent.parent.
writer.write(
"\t]\n"sv,
false);
776 .write(
"(flags)"sv,
false);
778 this->parent.writer.write(
" readonly"sv,
false);
781 this->parent.writer.write(
" report"sv,
false);
783 ::writeOptionalKeyValueStrings(this->parent.writer, {displayName, description});
784 this->parent.writer.write(
" =\n\t[\n"sv,
false);
789 this->parent.parent.writer
790 .write(
"\t\t"sv,
false)
791 .write(std::to_string(value),
false)
792 .write(
" : \""sv,
false)
793 .write(displayName,
false)
794 .write(
"\" : "sv,
false)
795 .write(std::to_string(enabledByDefault),
false);
796 if (!description.empty()) {
797 this->parent.parent.
writer
798 .write(
" : \""sv,
false)
799 .write(description,
false)
802 this->parent.parent.writer.write(
'\n');
807 this->parent.parent.
writer.write(
"\t]\n"sv,
false);
814 .write(
"input "sv,
false)
817 .write(valueType,
false)
819 if (!description.empty()) {
821 .write(
" : \""sv,
false)
822 .write(description,
false)
825 this->parent.writer <<
'\n';
832 .write(
"output "sv,
false)
835 .write(valueType,
false)
837 if (!description.empty()) {
839 .write(
" : \""sv,
false)
840 .write(description,
false)
843 this->parent.writer <<
'\n';
848 this->parent.
writer.write(
"]\n\n"sv,
false);
853 std::string_view out{this->
backingData.data(), this->writer.tell()};
854 while (out.ends_with(
"\n\n")) {
855 out = out.substr(0, out.size() - 1);
857 return std::string{out};
std::string readFileText(const std::string &filepath, std::size_t startOffset=0)
bool writeFileText(const std::string &filepath, const std::string &text)
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=DEFAULT_ESCAPE_SEQUENCES)
Read a string starting at the current stream position.
std::string_view readUnquotedStringToBuffer(BufferStream &stream, BufferStream &backing, const EscapeSequenceMap &escapeSequences=DEFAULT_ESCAPE_SEQUENCES)
Read a string starting at the current stream position.
const EscapeSequenceMap NO_ESCAPE_SEQUENCES
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.
void normalizeSlashes(std::string &path, bool stripSlashPrefix=false, bool stripSlashSuffix=true)
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)