diff options
| author | Richard-Qin-X <richard.qin.001@gmail.com> | 2026-08-01 16:47:16 +0800 |
|---|---|---|
| committer | Richard-Qin-X <richard.qin.001@gmail.com> | 2026-08-01 16:47:16 +0800 |
| commit | 85eed8fb2d6563fea750f7b55730c9feaecc5898 (patch) | |
| tree | 81663a7028c73e245ce573cd6b52e2a751146cd6 /backend/lib/Support | |
| parent | 924dcd36ebed84f04729de05ec08b311a36a04c1 (diff) | |
| download | rcc-project-85eed8fb2d6563fea750f7b55730c9feaecc5898.tar.gz rcc-project-85eed8fb2d6563fea750f7b55730c9feaecc5898.tar.xz | |
[Support] Add MIR YAML parsing infrastructure
Introduce source ranges, deterministic text diagnostics, and an independently authored YAML stream parser for the fixed LLVM MIR container shapes. Record the compatibility matrix and Stage 6 validation state.
Diffstat (limited to 'backend/lib/Support')
| -rw-r--r-- | backend/lib/Support/CMakeLists.txt | 12 | ||||
| -rw-r--r-- | backend/lib/Support/TextDiagnostic.cpp | 91 | ||||
| -rw-r--r-- | backend/lib/Support/YAML.cpp | 1217 |
3 files changed, 1318 insertions, 2 deletions
diff --git a/backend/lib/Support/CMakeLists.txt b/backend/lib/Support/CMakeLists.txt index 88c959d..ee85084 100644 --- a/backend/lib/Support/CMakeLists.txt +++ b/backend/lib/Support/CMakeLists.txt @@ -1,9 +1,11 @@ # SPDX-License-Identifier: GPL-3.0-or-later -set(RCCBackendSupportSources Error.cpp) +set(RCCBackendSupportSources Error.cpp TextDiagnostic.cpp YAML.cpp) set( RCCBackendSupportHeaders "${RCC_BACKEND_SOURCE_DIR}/include/rcc/Support/Error.h" + "${RCC_BACKEND_SOURCE_DIR}/include/rcc/Support/TextDiagnostic.h" + "${RCC_BACKEND_SOURCE_DIR}/include/rcc/Support/YAML.h" ) add_library(RCCBackendSupport ${RCCBackendSupportSources}) @@ -35,6 +37,12 @@ install( rcc_backend_register_format_files( "${CMAKE_CURRENT_SOURCE_DIR}/Error.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/TextDiagnostic.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/YAML.cpp" ${RCCBackendSupportHeaders} ) -rcc_backend_register_tidy_files("${CMAKE_CURRENT_SOURCE_DIR}/Error.cpp") +rcc_backend_register_tidy_files( + "${CMAKE_CURRENT_SOURCE_DIR}/Error.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/TextDiagnostic.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/YAML.cpp" +) diff --git a/backend/lib/Support/TextDiagnostic.cpp b/backend/lib/Support/TextDiagnostic.cpp new file mode 100644 index 0000000..1c027c3 --- /dev/null +++ b/backend/lib/Support/TextDiagnostic.cpp @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/Support/TextDiagnostic.h" + +#include <algorithm> +#include <iterator> +#include <sstream> +#include <utility> + +namespace rcc { + +std::string_view getDiagnosticSeverityName(DiagnosticSeverity Severity) noexcept { + switch (Severity) { + case DiagnosticSeverity::Note: + return "note"; + case DiagnosticSeverity::Warning: + return "warning"; + case DiagnosticSeverity::Error: + return "error"; + } + + return "unknown"; +} + +TextSource::TextSource(std::string Name, std::string Text) + : Name(std::move(Name)), Text(std::move(Text)), LineOffsets{0} { + for (std::size_t Index = 0; Index < this->Text.size(); ++Index) { + if (this->Text[Index] == '\n') + LineOffsets.push_back(Index + 1); + } +} + +TextPosition TextSource::positionAt(std::size_t Offset) const noexcept { + Offset = std::min(Offset, Text.size()); + const auto LineIterator = std::upper_bound(LineOffsets.begin(), LineOffsets.end(), Offset); + const std::size_t LineIndex = + static_cast<std::size_t>(std::distance(LineOffsets.begin(), LineIterator) - 1); + return TextPosition{ + .Offset = Offset, + .Line = LineIndex + 1, + .Column = Offset - LineOffsets[LineIndex] + 1, + }; +} + +TextRange TextSource::range(std::size_t Begin, std::size_t End) const noexcept { + Begin = std::min(Begin, Text.size()); + End = std::clamp(End, Begin, Text.size()); + return TextRange{.Begin = positionAt(Begin), .End = positionAt(End)}; +} + +std::string_view TextSource::line(std::size_t OneBasedLine) const noexcept { + if (OneBasedLine == 0 || OneBasedLine > LineOffsets.size()) + return {}; + + const std::size_t Begin = LineOffsets[OneBasedLine - 1]; + std::size_t End = OneBasedLine < LineOffsets.size() ? LineOffsets[OneBasedLine] - 1 : Text.size(); + if (End > Begin && Text[End - 1] == '\r') + --End; + return std::string_view(Text).substr(Begin, End - Begin); +} + +std::string formatTextDiagnostic(const TextDiagnostic &Diagnostic, const TextSource &Source) { + std::ostringstream Output; + const TextPosition Position = Diagnostic.Range.Begin; + const std::string_view SourceName = + Diagnostic.SourceName.empty() ? Source.name() : std::string_view(Diagnostic.SourceName); + + Output << SourceName << ':' << Position.Line << ':' << Position.Column << ": " + << getDiagnosticSeverityName(Diagnostic.Severity); + if (!Diagnostic.Category.empty()) + Output << '[' << Diagnostic.Category << ']'; + Output << ": " << Diagnostic.Message; + + const std::string_view SourceLine = Source.line(Position.Line); + if (SourceLine.empty()) + return Output.str(); + + Output << '\n' << SourceLine << '\n'; + const std::size_t PrefixWidth = Position.Column > 0 ? Position.Column - 1 : 0; + for (std::size_t Index = 0; Index < PrefixWidth && Index < SourceLine.size(); ++Index) + Output << (SourceLine[Index] == '\t' ? '\t' : ' '); + + Output << '^'; + if (Diagnostic.Range.End.Line == Position.Line && + Diagnostic.Range.End.Column > Position.Column + 1) { + Output << std::string(Diagnostic.Range.End.Column - Position.Column - 1, '~'); + } + return Output.str(); +} + +} // namespace rcc diff --git a/backend/lib/Support/YAML.cpp b/backend/lib/Support/YAML.cpp new file mode 100644 index 0000000..1d3ed2f --- /dev/null +++ b/backend/lib/Support/YAML.cpp @@ -0,0 +1,1217 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/Support/YAML.h" + +#include <algorithm> +#include <limits> +#include <optional> +#include <string> +#include <string_view> +#include <utility> + +namespace rcc::yaml { +namespace { + +struct PhysicalLine final { + std::size_t Begin = 0; + std::size_t ContentEnd = 0; + std::size_t End = 0; + std::size_t Indent = 0; + bool HasIndentTab = false; + bool IsBlank = true; +}; + +enum class DocumentMarker : std::uint8_t { + None, + Start, + End, +}; + +[[nodiscard]] bool isAsciiSpace(char Character) noexcept { + return Character == ' ' || Character == '\t'; +} + +[[nodiscard]] bool isHexDigit(char Character) noexcept { + return (Character >= '0' && Character <= '9') || (Character >= 'a' && Character <= 'f') || + (Character >= 'A' && Character <= 'F'); +} + +[[nodiscard]] unsigned hexValue(char Character) noexcept { + if (Character >= '0' && Character <= '9') + return static_cast<unsigned>(Character - '0'); + if (Character >= 'a' && Character <= 'f') + return static_cast<unsigned>(Character - 'a') + 10U; + return static_cast<unsigned>(Character - 'A') + 10U; +} + +[[nodiscard]] bool isNullSpelling(std::string_view Value) noexcept { + return Value == "~" || Value == "null" || Value == "Null" || Value == "NULL"; +} + +void appendUTF8(std::string &Output, std::uint32_t CodePoint) { + if (CodePoint <= 0x7FU) { + Output.push_back(static_cast<char>(CodePoint)); + return; + } + if (CodePoint <= 0x7FFU) { + Output.push_back(static_cast<char>(0xC0U | (CodePoint >> 6U))); + Output.push_back(static_cast<char>(0x80U | (CodePoint & 0x3FU))); + return; + } + if (CodePoint <= 0xFFFFU) { + Output.push_back(static_cast<char>(0xE0U | (CodePoint >> 12U))); + Output.push_back(static_cast<char>(0x80U | ((CodePoint >> 6U) & 0x3FU))); + Output.push_back(static_cast<char>(0x80U | (CodePoint & 0x3FU))); + return; + } + Output.push_back(static_cast<char>(0xF0U | (CodePoint >> 18U))); + Output.push_back(static_cast<char>(0x80U | ((CodePoint >> 12U) & 0x3FU))); + Output.push_back(static_cast<char>(0x80U | ((CodePoint >> 6U) & 0x3FU))); + Output.push_back(static_cast<char>(0x80U | (CodePoint & 0x3FU))); +} + +} // namespace + +std::optional<NodeID> Node::mappingValue(std::string_view Key) const noexcept { + if (Kind != NodeKind::Mapping) + return std::nullopt; + const auto Iterator = std::find_if(Mapping.begin(), Mapping.end(), + [Key](const MappingEntry &Entry) { return Entry.Key == Key; }); + if (Iterator == Mapping.end()) + return std::nullopt; + return Iterator->Value; +} + +const Node *Stream::node(NodeID ID) const noexcept { + if (!ID.isValid() || ID.rawValue() >= Nodes.size()) + return nullptr; + return &Nodes[ID.rawValue()]; +} + +class Parser final { +public: + Parser(std::string SourceName, std::string SourceText, ParseOptions Options) + : Parsed(std::move(SourceName), std::move(SourceText)), Options(Options) { + buildLineTable(); + } + + [[nodiscard]] ParseResult run() { + if (Options.MaximumDepth == 0) { + diagnose(0, 0, "maximum YAML nesting depth must be greater than zero"); + return failureResult(); + } + if (Options.MaximumNodes == 0 || + Options.MaximumNodes >= std::numeric_limits<std::uint32_t>::max()) { + diagnose(0, 0, "maximum YAML node count is outside the supported range"); + return failureResult(); + } + if (Parsed.Source.text().find('\0') != std::string_view::npos) { + const std::size_t Offset = Parsed.Source.text().find('\0'); + diagnose(Offset, Offset + 1, "NUL bytes are not allowed in YAML input"); + return failureResult(); + } + + std::size_t LineIndex = 0; + skipBlankLines(LineIndex, Lines.size()); + while (LineIndex < Lines.size()) { + if (Lines[LineIndex].Indent == 0 && Parsed.Source.text()[Lines[LineIndex].Begin] == '%') { + diagnose(Lines[LineIndex].Begin, Lines[LineIndex].ContentEnd, + "YAML directives are not supported"); + return failureResult(); + } + if (markerAt(LineIndex) == DocumentMarker::End) { + diagnose(Lines[LineIndex].Begin, Lines[LineIndex].ContentEnd, + "document end marker has no open document"); + return failureResult(); + } + + const std::size_t DocumentStartLine = LineIndex; + const bool HasExplicitStart = markerAt(LineIndex) == DocumentMarker::Start; + std::size_t InlineStart = Lines[LineIndex].ContentEnd; + if (HasExplicitStart) + InlineStart = markerRemainder(LineIndex, 3); + + const std::size_t SearchStart = HasExplicitStart ? LineIndex + 1 : LineIndex; + const std::size_t LimitLine = findDocumentBoundary(SearchStart); + NodeID Root; + if (HasExplicitStart && InlineStart < Lines[LineIndex].ContentEnd && + Parsed.Source.text()[InlineStart] != '#') { + if (Parsed.Source.text()[InlineStart] == '|' || Parsed.Source.text()[InlineStart] == '>') { + Root = parseBlockScalar(LineIndex, InlineStart, 0, 1, LimitLine); + } else { + Root = parseInlineValueOnLine(LineIndex, InlineStart, 1, LimitLine); + } + } else { + if (HasExplicitStart) + ++LineIndex; + skipBlankLines(LineIndex, LimitLine); + if (LineIndex == LimitLine) { + const std::size_t Offset = HasExplicitStart ? Lines[DocumentStartLine].ContentEnd + : Lines[DocumentStartLine].Begin; + Root = addNullNode(Offset, Offset); + } else { + Root = parseBlockNode(LineIndex, Lines[LineIndex].Indent, 1, LimitLine); + } + } + + if (Failed) + return failureResult(); + + skipBlankLines(LineIndex, LimitLine); + if (LineIndex != LimitLine) { + diagnose(Lines[LineIndex].Begin + Lines[LineIndex].Indent, Lines[LineIndex].ContentEnd, + "unexpected content after YAML document root"); + return failureResult(); + } + + bool HasExplicitEnd = false; + if (LimitLine < Lines.size() && markerAt(LimitLine) == DocumentMarker::End) { + const std::size_t EndRemainder = markerRemainder(LimitLine, 3); + if (EndRemainder < Lines[LimitLine].ContentEnd && + Parsed.Source.text()[EndRemainder] != '#') { + diagnose(EndRemainder, Lines[LimitLine].ContentEnd, + "document end marker may only be followed by a comment"); + return failureResult(); + } + HasExplicitEnd = true; + LineIndex = LimitLine + 1; + } else { + LineIndex = LimitLine; + } + + const std::size_t DocumentBegin = Lines[DocumentStartLine].Begin; + const std::size_t DocumentEnd = HasExplicitEnd + ? Lines[LimitLine].ContentEnd + : (LimitLine == 0 ? 0 : Lines[LimitLine - 1].End); + Parsed.Documents.push_back(Document{ + .Root = Root, + .Range = Parsed.Source.range(DocumentBegin, DocumentEnd), + .HasExplicitStart = HasExplicitStart, + .HasExplicitEnd = HasExplicitEnd, + }); + + skipBlankLines(LineIndex, Lines.size()); + } + + ParseResult Result; + Result.ParsedStream.emplace(std::move(Parsed)); + return Result; + } + +private: + void buildLineTable() { + const std::string_view Text = Parsed.Source.text(); + std::size_t Begin = 0; + while (Begin <= Text.size()) { + const std::size_t Newline = Text.find('\n', Begin); + const std::size_t End = Newline == std::string_view::npos ? Text.size() : Newline + 1; + std::size_t ContentEnd = Newline == std::string_view::npos ? Text.size() : Newline; + if (ContentEnd > Begin && Text[ContentEnd - 1] == '\r') + --ContentEnd; + + PhysicalLine Line{ + .Begin = Begin, + .ContentEnd = ContentEnd, + .End = End, + }; + std::size_t Cursor = Begin; + while (Cursor < ContentEnd && Text[Cursor] == ' ') + ++Cursor; + Line.Indent = Cursor - Begin; + std::size_t ContentCursor = Cursor; + while (ContentCursor < ContentEnd && isAsciiSpace(Text[ContentCursor])) { + if (Text[ContentCursor] == '\t') + Line.HasIndentTab = true; + ++ContentCursor; + } + Line.IsBlank = ContentCursor == ContentEnd || Text[ContentCursor] == '#'; + Lines.push_back(Line); + + if (Newline == std::string_view::npos) + break; + Begin = End; + } + } + + [[nodiscard]] ParseResult failureResult() { + ParseResult Result; + Result.Diagnostics = std::move(Diagnostics); + return Result; + } + + void diagnose(std::size_t Begin, std::size_t End, std::string Message) { + if (Failed) + return; + Failed = true; + Diagnostics.push_back(TextDiagnostic{ + .Severity = DiagnosticSeverity::Error, + .Category = "yaml", + .Message = std::move(Message), + .SourceName = std::string(Parsed.Source.name()), + .Range = Parsed.Source.range(Begin, End), + }); + } + + void skipBlankLines(std::size_t &LineIndex, std::size_t LimitLine) { + while (LineIndex < LimitLine && Lines[LineIndex].IsBlank) + ++LineIndex; + } + + [[nodiscard]] DocumentMarker markerAt(std::size_t LineIndex) const noexcept { + if (LineIndex >= Lines.size()) + return DocumentMarker::None; + const PhysicalLine &Line = Lines[LineIndex]; + if (Line.Indent != 0 || Line.ContentEnd - Line.Begin < 3) + return DocumentMarker::None; + + const std::string_view Text = Parsed.Source.text(); + const std::string_view Prefix = Text.substr(Line.Begin, 3); + if (Prefix != "---" && Prefix != "...") + return DocumentMarker::None; + if (Line.Begin + 3 < Line.ContentEnd) { + const char Next = Text[Line.Begin + 3]; + if (!isAsciiSpace(Next) && Next != '#') + return DocumentMarker::None; + } + return Prefix == "---" ? DocumentMarker::Start : DocumentMarker::End; + } + + [[nodiscard]] std::size_t markerRemainder(std::size_t LineIndex, + std::size_t MarkerWidth) const noexcept { + const PhysicalLine &Line = Lines[LineIndex]; + std::size_t Cursor = Line.Begin + MarkerWidth; + const std::string_view Text = Parsed.Source.text(); + while (Cursor < Line.ContentEnd && isAsciiSpace(Text[Cursor])) + ++Cursor; + return Cursor; + } + + [[nodiscard]] std::size_t findDocumentBoundary(std::size_t StartLine) const noexcept { + for (std::size_t LineIndex = StartLine; LineIndex < Lines.size(); ++LineIndex) { + if (markerAt(LineIndex) != DocumentMarker::None) + return LineIndex; + } + return Lines.size(); + } + + [[nodiscard]] bool validateIndent(std::size_t LineIndex) { + if (!Lines[LineIndex].HasIndentTab) + return true; + diagnose(Lines[LineIndex].Begin, Lines[LineIndex].Begin + Lines[LineIndex].Indent, + "tab characters are not allowed in YAML indentation"); + return false; + } + + [[nodiscard]] NodeID parseBlockNode(std::size_t &LineIndex, std::size_t Indent, std::size_t Depth, + std::size_t LimitLine) { + if (!checkDepth(Depth)) + return {}; + skipBlankLines(LineIndex, LimitLine); + if (LineIndex >= LimitLine) { + const std::size_t Offset = + LimitLine < Lines.size() ? Lines[LimitLine].Begin : Parsed.Source.text().size(); + return addNullNode(Offset, Offset); + } + if (!validateIndent(LineIndex)) + return {}; + if (Lines[LineIndex].Indent != Indent) { + diagnose(Lines[LineIndex].Begin + Lines[LineIndex].Indent, Lines[LineIndex].ContentEnd, + "inconsistent YAML indentation"); + return {}; + } + + const std::size_t ContentStart = Lines[LineIndex].Begin + Indent; + if (isSequenceIndicator(ContentStart, Lines[LineIndex].ContentEnd)) + return parseBlockSequence(LineIndex, Indent, Depth, LimitLine); + if (findBlockMappingColon(ContentStart, Lines[LineIndex].ContentEnd)) + return parseBlockMapping(LineIndex, Indent, Depth, LimitLine); + + NodeID Result; + if (Parsed.Source.text()[ContentStart] == '|' || Parsed.Source.text()[ContentStart] == '>') { + Result = parseBlockScalar(LineIndex, ContentStart, Indent, Depth, LimitLine); + } else { + Result = parseInlineValueOnLine(LineIndex, ContentStart, Depth, LimitLine); + } + return Result; + } + + [[nodiscard]] NodeID parseBlockMapping(std::size_t &LineIndex, std::size_t Indent, + std::size_t Depth, std::size_t LimitLine) { + const std::size_t Begin = Lines[LineIndex].Begin + Indent; + std::vector<MappingEntry> Entries; + while (LineIndex < LimitLine) { + skipBlankLines(LineIndex, LimitLine); + if (LineIndex >= LimitLine || Lines[LineIndex].Indent < Indent) + break; + if (!validateIndent(LineIndex)) + return {}; + if (Lines[LineIndex].Indent > Indent) { + diagnose(Lines[LineIndex].Begin + Lines[LineIndex].Indent, Lines[LineIndex].ContentEnd, + "unexpected indentation in YAML mapping"); + return {}; + } + + const std::size_t KeyStart = Lines[LineIndex].Begin + Indent; + if (!findBlockMappingColon(KeyStart, Lines[LineIndex].ContentEnd)) + break; + if (!parseMappingEntry(LineIndex, KeyStart, Indent, Depth, LimitLine, Entries)) + return {}; + } + + const std::size_t End = + LineIndex == 0 ? Begin : std::max(Begin, Lines[LineIndex - 1].ContentEnd); + return addMappingNode(Begin, End, CollectionStyle::Block, std::move(Entries)); + } + + [[nodiscard]] NodeID parseInlineStartedMapping(std::size_t &LineIndex, std::size_t KeyStart, + std::size_t MappingIndent, std::size_t Depth, + std::size_t LimitLine) { + const std::size_t Begin = KeyStart; + std::vector<MappingEntry> Entries; + if (!parseMappingEntry(LineIndex, KeyStart, MappingIndent, Depth, LimitLine, Entries)) + return {}; + + while (LineIndex < LimitLine) { + skipBlankLines(LineIndex, LimitLine); + if (LineIndex >= LimitLine || Lines[LineIndex].Indent < MappingIndent) + break; + if (!validateIndent(LineIndex)) + return {}; + if (Lines[LineIndex].Indent > MappingIndent) { + diagnose(Lines[LineIndex].Begin + Lines[LineIndex].Indent, Lines[LineIndex].ContentEnd, + "unexpected indentation in YAML mapping"); + return {}; + } + const std::size_t ContinuationKey = Lines[LineIndex].Begin + MappingIndent; + if (!findBlockMappingColon(ContinuationKey, Lines[LineIndex].ContentEnd)) + break; + if (!parseMappingEntry(LineIndex, ContinuationKey, MappingIndent, Depth, LimitLine, Entries)) + return {}; + } + + const std::size_t End = + LineIndex == 0 ? Begin : std::max(Begin, Lines[LineIndex - 1].ContentEnd); + return addMappingNode(Begin, End, CollectionStyle::Block, std::move(Entries)); + } + + [[nodiscard]] bool parseMappingEntry(std::size_t &LineIndex, std::size_t KeyStart, + std::size_t ParentIndent, std::size_t Depth, + std::size_t LimitLine, std::vector<MappingEntry> &Entries) { + const PhysicalLine &Line = Lines[LineIndex]; + const std::optional<std::size_t> Colon = findBlockMappingColon(KeyStart, Line.ContentEnd); + if (!Colon) { + diagnose(KeyStart, Line.ContentEnd, "expected ':' after YAML mapping key"); + return false; + } + + std::string Key; + TextRange KeyRange; + if (!parseMappingKey(KeyStart, *Colon, Key, KeyRange)) + return false; + if (std::any_of(Entries.begin(), Entries.end(), + [&Key](const MappingEntry &Entry) { return Entry.Key == Key; })) { + diagnose(KeyStart, *Colon, "duplicate YAML mapping key '" + Key + "'"); + return false; + } + + std::size_t ValueStart = *Colon + 1; + const std::string_view Text = Parsed.Source.text(); + while (ValueStart < Line.ContentEnd && isAsciiSpace(Text[ValueStart])) + ++ValueStart; + + NodeID Value; + if (ValueStart == Line.ContentEnd || Text[ValueStart] == '#') { + const std::size_t CurrentLine = LineIndex; + std::size_t NestedLine = LineIndex + 1; + skipBlankLines(NestedLine, LimitLine); + if (NestedLine < LimitLine && Lines[NestedLine].Indent > ParentIndent) { + LineIndex = NestedLine; + Value = parseBlockNode(LineIndex, Lines[NestedLine].Indent, Depth + 1, LimitLine); + } else { + Value = addNullNode(*Colon + 1, *Colon + 1); + LineIndex = CurrentLine + 1; + } + } else if (Text[ValueStart] == '|' || Text[ValueStart] == '>') { + Value = parseBlockScalar(LineIndex, ValueStart, ParentIndent, Depth + 1, LimitLine); + } else { + Value = parseInlineValueOnLine(LineIndex, ValueStart, Depth + 1, LimitLine); + } + if (Failed) + return false; + + Entries.push_back(MappingEntry{ + .Key = std::move(Key), + .KeyRange = KeyRange, + .Value = Value, + }); + return true; + } + + [[nodiscard]] NodeID parseBlockSequence(std::size_t &LineIndex, std::size_t Indent, + std::size_t Depth, std::size_t LimitLine) { + const std::size_t Begin = Lines[LineIndex].Begin + Indent; + std::vector<NodeID> Items; + const std::string_view Text = Parsed.Source.text(); + + while (LineIndex < LimitLine) { + skipBlankLines(LineIndex, LimitLine); + if (LineIndex >= LimitLine || Lines[LineIndex].Indent < Indent) + break; + if (!validateIndent(LineIndex)) + return {}; + if (Lines[LineIndex].Indent > Indent) { + diagnose(Lines[LineIndex].Begin + Lines[LineIndex].Indent, Lines[LineIndex].ContentEnd, + "unexpected indentation in YAML sequence"); + return {}; + } + + const std::size_t Dash = Lines[LineIndex].Begin + Indent; + if (!isSequenceIndicator(Dash, Lines[LineIndex].ContentEnd)) + break; + + std::size_t ItemStart = Dash + 1; + while (ItemStart < Lines[LineIndex].ContentEnd && isAsciiSpace(Text[ItemStart])) + ++ItemStart; + + NodeID Item; + if (ItemStart == Lines[LineIndex].ContentEnd || Text[ItemStart] == '#') { + const std::size_t CurrentLine = LineIndex; + std::size_t NestedLine = LineIndex + 1; + skipBlankLines(NestedLine, LimitLine); + if (NestedLine < LimitLine && Lines[NestedLine].Indent > Indent) { + LineIndex = NestedLine; + Item = parseBlockNode(LineIndex, Lines[NestedLine].Indent, Depth + 1, LimitLine); + } else { + Item = addNullNode(Dash + 1, Dash + 1); + LineIndex = CurrentLine + 1; + } + } else if (Text[ItemStart] == '|' || Text[ItemStart] == '>') { + Item = parseBlockScalar(LineIndex, ItemStart, Indent, Depth + 1, LimitLine); + } else if (findBlockMappingColon(ItemStart, Lines[LineIndex].ContentEnd)) { + const std::size_t MappingIndent = ItemStart - Lines[LineIndex].Begin; + Item = parseInlineStartedMapping(LineIndex, ItemStart, MappingIndent, Depth + 1, LimitLine); + } else { + Item = parseInlineValueOnLine(LineIndex, ItemStart, Depth + 1, LimitLine); + } + if (Failed) + return {}; + Items.push_back(Item); + } + + const std::size_t End = + LineIndex == 0 ? Begin : std::max(Begin, Lines[LineIndex - 1].ContentEnd); + return addSequenceNode(Begin, End, CollectionStyle::Block, std::move(Items)); + } + + [[nodiscard]] NodeID parseInlineValueOnLine(std::size_t &LineIndex, std::size_t Begin, + std::size_t Depth, std::size_t LimitLine) { + const std::string_view Text = Parsed.Source.text(); + const std::size_t InitialLine = LineIndex; + const std::size_t LineEnd = Lines[InitialLine].ContentEnd; + std::size_t ValueStart = Begin; + while (ValueStart < LineEnd && isAsciiSpace(Text[ValueStart])) + ++ValueStart; + + const bool CanSpanLines = + ValueStart < LineEnd && (Text[ValueStart] == '[' || Text[ValueStart] == '{' || + Text[ValueStart] == '\'' || Text[ValueStart] == '"'); + if (!CanSpanLines) { + NodeID Result = parseInlineValue(ValueStart, LineEnd, Depth); + ++LineIndex; + return Result; + } + + const std::size_t DocumentEnd = + LimitLine < Lines.size() ? Lines[LimitLine].Begin : Parsed.Source.text().size(); + std::size_t Cursor = ValueStart; + NodeID Result = parseFlowNode(Cursor, DocumentEnd, Depth, false); + if (Failed) + return {}; + + const std::size_t LastValueOffset = Cursor == 0 ? 0 : Cursor - 1; + const std::size_t LastLine = Parsed.Source.positionAt(LastValueOffset).Line - 1; + if (LastLine >= Lines.size()) { + diagnose(Cursor, Cursor, "internal YAML line tracking failure"); + return {}; + } + + std::size_t Tail = Cursor; + while (Tail < Lines[LastLine].ContentEnd && isAsciiSpace(Text[Tail])) + ++Tail; + if (Tail < Lines[LastLine].ContentEnd && Text[Tail] != '#') { + diagnose(Tail, Lines[LastLine].ContentEnd, "unexpected characters after YAML value"); + return {}; + } + LineIndex = LastLine + 1; + return Result; + } + + [[nodiscard]] NodeID parseInlineValue(std::size_t Begin, std::size_t End, std::size_t Depth) { + if (!checkDepth(Depth)) + return {}; + const std::string_view Text = Parsed.Source.text(); + while (Begin < End && isAsciiSpace(Text[Begin])) + ++Begin; + if (Begin == End || Text[Begin] == '#') + return addNullNode(Begin, Begin); + + std::size_t Cursor = Begin; + NodeID Result = parseFlowNode(Cursor, End, Depth, false); + if (Failed) + return {}; + + while (Cursor < End && isAsciiSpace(Text[Cursor])) + ++Cursor; + if (Cursor < End && Text[Cursor] != '#') { + diagnose(Cursor, End, "unexpected characters after YAML value"); + return {}; + } + return Result; + } + + [[nodiscard]] NodeID parseFlowNode(std::size_t &Cursor, std::size_t End, std::size_t Depth, + bool IsFlowValue) { + if (!checkDepth(Depth)) + return {}; + const std::string_view Text = Parsed.Source.text(); + while (Cursor < End && isAsciiSpace(Text[Cursor])) + ++Cursor; + if (Cursor >= End) { + diagnose(End, End, "expected YAML value"); + return {}; + } + + if (Text[Cursor] == '[') + return parseFlowSequence(Cursor, End, Depth); + if (Text[Cursor] == '{') + return parseFlowMapping(Cursor, End, Depth); + if (Text[Cursor] == '\'' || Text[Cursor] == '"') + return parseQuotedScalar(Cursor, End); + if (Text[Cursor] == '&' || Text[Cursor] == '*' || Text[Cursor] == '!' || Text[Cursor] == '%') { + diagnose(Cursor, Cursor + 1, "YAML anchors, aliases, tags, and directives are not supported"); + return {}; + } + + const std::size_t Begin = Cursor; + while (Cursor < End) { + const char Character = Text[Cursor]; + if (IsFlowValue && (Character == ',' || Character == ']' || Character == '}')) + break; + if (Character == '#' && (Cursor == Begin || isAsciiSpace(Text[Cursor - 1]))) + break; + ++Cursor; + } + std::size_t ScalarEnd = Cursor; + while (ScalarEnd > Begin && isAsciiSpace(Text[ScalarEnd - 1])) + --ScalarEnd; + if (ScalarEnd == Begin) { + diagnose(Begin, std::min(Begin + 1, End), "expected YAML scalar"); + return {}; + } + const std::string Value(Text.substr(Begin, ScalarEnd - Begin)); + return isNullSpelling(Value) ? addNullNode(Begin, ScalarEnd) + : addScalarNode(Begin, ScalarEnd, ScalarStyle::Plain, Value); + } + + [[nodiscard]] NodeID parseFlowSequence(std::size_t &Cursor, std::size_t End, std::size_t Depth) { + const std::size_t Begin = Cursor++; + std::vector<NodeID> Items; + const std::string_view Text = Parsed.Source.text(); + skipFlowSpace(Cursor, End); + if (Cursor < End && Text[Cursor] == ']') { + ++Cursor; + return addSequenceNode(Begin, Cursor, CollectionStyle::Flow, std::move(Items)); + } + + while (Cursor < End) { + Items.push_back(parseFlowNode(Cursor, End, Depth + 1, true)); + if (Failed) + return {}; + skipFlowSpace(Cursor, End); + if (Cursor >= End) { + diagnose(Begin, End, "unterminated YAML flow sequence"); + return {}; + } + if (Text[Cursor] == ']') { + ++Cursor; + return addSequenceNode(Begin, Cursor, CollectionStyle::Flow, std::move(Items)); + } + if (Text[Cursor] != ',') { + diagnose(Cursor, Cursor + 1, "expected ',' or ']' in YAML flow sequence"); + return {}; + } + ++Cursor; + skipFlowSpace(Cursor, End); + if (Cursor < End && Text[Cursor] == ']') { + ++Cursor; + return addSequenceNode(Begin, Cursor, CollectionStyle::Flow, std::move(Items)); + } + } + + diagnose(Begin, End, "unterminated YAML flow sequence"); + return {}; + } + + [[nodiscard]] NodeID parseFlowMapping(std::size_t &Cursor, std::size_t End, std::size_t Depth) { + const std::size_t Begin = Cursor++; + std::vector<MappingEntry> Entries; + const std::string_view Text = Parsed.Source.text(); + skipFlowSpace(Cursor, End); + if (Cursor < End && Text[Cursor] == '}') { + ++Cursor; + return addMappingNode(Begin, Cursor, CollectionStyle::Flow, std::move(Entries)); + } + + while (Cursor < End) { + std::string Key; + TextRange KeyRange; + if (!parseFlowKey(Cursor, End, Key, KeyRange)) + return {}; + if (std::any_of(Entries.begin(), Entries.end(), + [&Key](const MappingEntry &Entry) { return Entry.Key == Key; })) { + diagnose(KeyRange.Begin.Offset, KeyRange.End.Offset, + "duplicate YAML mapping key '" + Key + "'"); + return {}; + } + skipFlowSpace(Cursor, End); + if (Cursor >= End || Text[Cursor] != ':') { + diagnose(Cursor, std::min(Cursor + 1, End), "expected ':' after YAML flow mapping key"); + return {}; + } + ++Cursor; + skipFlowSpace(Cursor, End); + NodeID Value; + if (isFlowBlockSequenceStart(Cursor, End)) + Value = parseFlowBlockSequence(Cursor, End, Depth + 1); + else if (Cursor < End && (Text[Cursor] == ',' || Text[Cursor] == '}')) + Value = addNullNode(Cursor, Cursor); + else + Value = parseFlowNode(Cursor, End, Depth + 1, true); + if (Failed) + return {}; + Entries.push_back(MappingEntry{ + .Key = std::move(Key), + .KeyRange = KeyRange, + .Value = Value, + }); + + skipFlowSpace(Cursor, End); + if (Cursor >= End) { + diagnose(Begin, End, "unterminated YAML flow mapping"); + return {}; + } + if (Text[Cursor] == '}') { + ++Cursor; + return addMappingNode(Begin, Cursor, CollectionStyle::Flow, std::move(Entries)); + } + if (Text[Cursor] != ',') { + diagnose(Cursor, Cursor + 1, "expected ',' or '}' in YAML flow mapping"); + return {}; + } + ++Cursor; + skipFlowSpace(Cursor, End); + if (Cursor < End && Text[Cursor] == '}') { + ++Cursor; + return addMappingNode(Begin, Cursor, CollectionStyle::Flow, std::move(Entries)); + } + } + + diagnose(Begin, End, "unterminated YAML flow mapping"); + return {}; + } + + [[nodiscard]] NodeID parseFlowBlockSequence(std::size_t &Cursor, std::size_t End, + std::size_t Depth) { + if (!checkDepth(Depth)) + return {}; + const std::size_t Begin = Cursor; + const std::size_t FirstLine = Parsed.Source.positionAt(Cursor).Line - 1; + const std::size_t Indent = Lines[FirstLine].Indent; + const std::string_view Text = Parsed.Source.text(); + std::vector<NodeID> Items; + std::size_t LastItemEnd = Begin; + + while (isFlowBlockSequenceStart(Cursor, End)) { + const std::size_t ItemLine = Parsed.Source.positionAt(Cursor).Line - 1; + if (Lines[ItemLine].Indent != Indent) + break; + + const std::size_t Dash = Cursor++; + while (Cursor < Lines[ItemLine].ContentEnd && isAsciiSpace(Text[Cursor])) + ++Cursor; + if (Cursor == Lines[ItemLine].ContentEnd || Text[Cursor] == '#') { + diagnose(Dash, Lines[ItemLine].ContentEnd, + "empty block sequence entry inside a flow mapping is not supported"); + return {}; + } + + const bool ItemCanSpan = + Text[Cursor] == '[' || Text[Cursor] == '{' || Text[Cursor] == '\'' || Text[Cursor] == '"'; + const std::size_t ItemEnd = ItemCanSpan ? End : Lines[ItemLine].ContentEnd; + Items.push_back(parseFlowNode(Cursor, ItemEnd, Depth + 1, true)); + if (Failed) + return {}; + LastItemEnd = Cursor; + skipFlowSpace(Cursor, End); + } + + return addSequenceNode(Begin, LastItemEnd, CollectionStyle::Block, std::move(Items)); + } + + [[nodiscard]] bool parseFlowKey(std::size_t &Cursor, std::size_t End, std::string &Key, + TextRange &Range) { + const std::string_view Text = Parsed.Source.text(); + skipFlowSpace(Cursor, End); + if (Cursor >= End) { + diagnose(End, End, "expected YAML flow mapping key"); + return false; + } + + const std::size_t Begin = Cursor; + if (Text[Cursor] == '\'' || Text[Cursor] == '"') { + ScalarStyle Style = ScalarStyle::Plain; + return parseQuotedText(Cursor, End, Key, Style, Range); + } + + while (Cursor < End && Text[Cursor] != ':' && Text[Cursor] != ',' && Text[Cursor] != '}') + ++Cursor; + std::size_t KeyEnd = Cursor; + while (KeyEnd > Begin && isAsciiSpace(Text[KeyEnd - 1])) + --KeyEnd; + if (KeyEnd == Begin) { + diagnose(Begin, std::min(Begin + 1, End), "empty YAML mapping keys are not supported"); + return false; + } + Key = std::string(Text.substr(Begin, KeyEnd - Begin)); + Range = Parsed.Source.range(Begin, KeyEnd); + return true; + } + + [[nodiscard]] NodeID parseQuotedScalar(std::size_t &Cursor, std::size_t End) { + std::string Value; + ScalarStyle Style = ScalarStyle::Plain; + TextRange Range; + if (!parseQuotedText(Cursor, End, Value, Style, Range)) + return {}; + return addScalarNode(Range.Begin.Offset, Range.End.Offset, Style, std::move(Value)); + } + + [[nodiscard]] bool parseQuotedText(std::size_t &Cursor, std::size_t End, std::string &Value, + ScalarStyle &Style, TextRange &Range) { + const std::string_view Text = Parsed.Source.text(); + const std::size_t Begin = Cursor; + const char Quote = Text[Cursor++]; + + while (Cursor < End) { + const char Character = Text[Cursor++]; + if (Character == Quote) { + if (Quote == '\'' && Cursor < End && Text[Cursor] == '\'') { + Value.push_back('\''); + ++Cursor; + continue; + } + Style = Quote == '\'' ? ScalarStyle::SingleQuoted : ScalarStyle::DoubleQuoted; + Range = Parsed.Source.range(Begin, Cursor); + return true; + } + + if (Quote == '\'' || Character != '\\') { + if (static_cast<unsigned char>(Character) < 0x20U && Character != '\t') { + diagnose(Cursor - 1, Cursor, "control character is not allowed in quoted YAML scalar"); + return false; + } + Value.push_back(Character); + continue; + } + + if (Cursor >= End) { + diagnose(Cursor - 1, Cursor, "unterminated escape in YAML scalar"); + return false; + } + const char Escape = Text[Cursor++]; + switch (Escape) { + case '0': + Value.push_back('\0'); + break; + case 'a': + Value.push_back('\a'); + break; + case 'b': + Value.push_back('\b'); + break; + case 't': + case '\t': + Value.push_back('\t'); + break; + case 'n': + Value.push_back('\n'); + break; + case 'v': + Value.push_back('\v'); + break; + case 'f': + Value.push_back('\f'); + break; + case 'r': + Value.push_back('\r'); + break; + case 'e': + Value.push_back(static_cast<char>(0x1B)); + break; + case ' ': + Value.push_back(' '); + break; + case '"': + Value.push_back('"'); + break; + case '/': + Value.push_back('/'); + break; + case '\\': + Value.push_back('\\'); + break; + case 'N': + appendUTF8(Value, 0x85U); + break; + case '_': + appendUTF8(Value, 0xA0U); + break; + case 'L': + appendUTF8(Value, 0x2028U); + break; + case 'P': + appendUTF8(Value, 0x2029U); + break; + case 'x': + if (!parseUnicodeEscape(Cursor, End, 2, Value)) + return false; + break; + case 'u': + if (!parseUnicodeEscape(Cursor, End, 4, Value)) + return false; + break; + case 'U': + if (!parseUnicodeEscape(Cursor, End, 8, Value)) + return false; + break; + default: + diagnose(Cursor - 1, Cursor, "unknown escape in double-quoted YAML scalar"); + return false; + } + } + + diagnose(Begin, End, "unterminated quoted YAML scalar"); + return false; + } + + [[nodiscard]] bool parseUnicodeEscape(std::size_t &Cursor, std::size_t End, + std::size_t DigitCount, std::string &Output) { + if (End - Cursor < DigitCount) { + diagnose(Cursor, End, "incomplete Unicode escape in YAML scalar"); + return false; + } + std::uint32_t CodePoint = 0; + for (std::size_t Index = 0; Index < DigitCount; ++Index) { + if (!isHexDigit(Parsed.Source.text()[Cursor + Index])) { + diagnose(Cursor + Index, Cursor + Index + 1, + "invalid hexadecimal digit in YAML Unicode escape"); + return false; + } + CodePoint = (CodePoint << 4U) | hexValue(Parsed.Source.text()[Cursor + Index]); + } + Cursor += DigitCount; + if (CodePoint > 0x10FFFFU || (CodePoint >= 0xD800U && CodePoint <= 0xDFFFU)) { + diagnose(Cursor - DigitCount, Cursor, "invalid Unicode scalar value in YAML escape"); + return false; + } + appendUTF8(Output, CodePoint); + return true; + } + + [[nodiscard]] NodeID parseBlockScalar(std::size_t &LineIndex, std::size_t IndicatorStart, + std::size_t ParentIndent, std::size_t Depth, + std::size_t LimitLine) { + if (!checkDepth(Depth)) + return {}; + const std::string_view Text = Parsed.Source.text(); + const char Indicator = Text[IndicatorStart]; + char Chomping = '\0'; + std::optional<unsigned> ExplicitIndent; + std::size_t Cursor = IndicatorStart + 1; + while (Cursor < Lines[LineIndex].ContentEnd && !isAsciiSpace(Text[Cursor]) && + Text[Cursor] != '#') { + const char Header = Text[Cursor++]; + if ((Header == '+' || Header == '-') && Chomping == '\0') { + Chomping = Header; + continue; + } + if (Header >= '1' && Header <= '9' && !ExplicitIndent) { + ExplicitIndent = static_cast<unsigned>(Header - '0'); + continue; + } + diagnose(Cursor - 1, Cursor, "invalid YAML block scalar header"); + return {}; + } + while (Cursor < Lines[LineIndex].ContentEnd && isAsciiSpace(Text[Cursor])) + ++Cursor; + if (Cursor < Lines[LineIndex].ContentEnd && Text[Cursor] != '#') { + diagnose(Cursor, Lines[LineIndex].ContentEnd, + "unexpected characters after YAML block scalar header"); + return {}; + } + + const std::size_t HeaderLine = LineIndex; + std::size_t FirstContentLine = LineIndex + 1; + std::size_t FirstNonBlank = FirstContentLine; + while (FirstNonBlank < LimitLine && isWhitespaceOnlyLine(FirstNonBlank)) + ++FirstNonBlank; + + std::size_t ContentIndent = ParentIndent + 1; + if (ExplicitIndent) + ContentIndent = ParentIndent + *ExplicitIndent; + else if (FirstNonBlank < LimitLine) + ContentIndent = Lines[FirstNonBlank].Indent; + + if (FirstNonBlank < LimitLine && Lines[FirstNonBlank].Indent <= ParentIndent) { + FirstContentLine = FirstNonBlank; + ContentIndent = ParentIndent + 1; + } else if (FirstNonBlank < LimitLine && Lines[FirstNonBlank].Indent < ContentIndent) { + diagnose(Lines[FirstNonBlank].Begin, Lines[FirstNonBlank].ContentEnd, + "block scalar content is less indented than its header"); + return {}; + } + + struct ScalarLine final { + std::string Text; + bool HasLineBreak = false; + bool IsBlank = false; + bool IsMoreIndented = false; + }; + std::vector<ScalarLine> ScalarLines; + std::size_t CurrentLine = FirstContentLine; + while (CurrentLine < LimitLine) { + const PhysicalLine &Line = Lines[CurrentLine]; + const bool IsWhitespaceOnly = isWhitespaceOnlyLine(CurrentLine); + if (!IsWhitespaceOnly && Line.Indent < ContentIndent) + break; + + ScalarLine ScalarPart; + ScalarPart.HasLineBreak = Line.End > Line.ContentEnd; + ScalarPart.IsBlank = IsWhitespaceOnly; + ScalarPart.IsMoreIndented = !IsWhitespaceOnly && Line.Indent > ContentIndent; + if (!IsWhitespaceOnly) { + const std::size_t TextBegin = Line.Begin + ContentIndent; + ScalarPart.Text = std::string(Text.substr(TextBegin, Line.ContentEnd - TextBegin)); + } + ScalarLines.push_back(std::move(ScalarPart)); + ++CurrentLine; + } + + std::string Value; + for (std::size_t Index = 0; Index < ScalarLines.size(); ++Index) { + const ScalarLine &Line = ScalarLines[Index]; + Value += Line.Text; + if (!Line.HasLineBreak) + continue; + + bool FoldBreak = false; + if (Indicator == '>' && Index + 1 < ScalarLines.size()) { + const ScalarLine &Next = ScalarLines[Index + 1]; + FoldBreak = !Line.IsBlank && !Next.IsBlank && !Line.IsMoreIndented && !Next.IsMoreIndented; + } + Value.push_back(FoldBreak ? ' ' : '\n'); + } + + if (Chomping != '+') { + while (!Value.empty() && Value.back() == '\n') + Value.pop_back(); + if (Chomping != '-' && !ScalarLines.empty() && ScalarLines.back().HasLineBreak) + Value.push_back('\n'); + } + + LineIndex = CurrentLine; + const std::size_t End = CurrentLine > FirstContentLine ? Lines[CurrentLine - 1].ContentEnd + : Lines[HeaderLine].ContentEnd; + return addScalarNode(IndicatorStart, End, + Indicator == '|' ? ScalarStyle::Literal : ScalarStyle::Folded, + std::move(Value)); + } + + [[nodiscard]] bool parseMappingKey(std::size_t Begin, std::size_t End, std::string &Key, + TextRange &Range) { + const std::string_view Text = Parsed.Source.text(); + while (Begin < End && isAsciiSpace(Text[Begin])) + ++Begin; + while (End > Begin && isAsciiSpace(Text[End - 1])) + --End; + if (Begin == End) { + diagnose(Begin, End, "empty YAML mapping keys are not supported"); + return false; + } + + if (Text[Begin] == '\'' || Text[Begin] == '"') { + std::size_t Cursor = Begin; + ScalarStyle Style = ScalarStyle::Plain; + if (!parseQuotedText(Cursor, End, Key, Style, Range)) + return false; + while (Cursor < End && isAsciiSpace(Text[Cursor])) + ++Cursor; + if (Cursor != End) { + diagnose(Cursor, End, "unexpected characters after quoted YAML mapping key"); + return false; + } + return true; + } + if (Text[Begin] == '?' || Text[Begin] == '[' || Text[Begin] == '{') { + diagnose(Begin, End, "complex YAML mapping keys are not supported"); + return false; + } + Key = std::string(Text.substr(Begin, End - Begin)); + Range = Parsed.Source.range(Begin, End); + return true; + } + + [[nodiscard]] std::optional<std::size_t> findBlockMappingColon(std::size_t Begin, + std::size_t End) const noexcept { + const std::string_view Text = Parsed.Source.text(); + char Quote = '\0'; + unsigned FlowDepth = 0; + for (std::size_t Cursor = Begin; Cursor < End; ++Cursor) { + const char Character = Text[Cursor]; + if (Quote == '\'') { + if (Character == '\'' && Cursor + 1 < End && Text[Cursor + 1] == '\'') { + ++Cursor; + } else if (Character == '\'') { + Quote = '\0'; + } + continue; + } + if (Quote == '"') { + if (Character == '\\' && Cursor + 1 < End) { + ++Cursor; + } else if (Character == '"') { + Quote = '\0'; + } + continue; + } + if (Character == '\'' || Character == '"') { + Quote = Character; + continue; + } + if (Character == '[' || Character == '{') { + ++FlowDepth; + continue; + } + if ((Character == ']' || Character == '}') && FlowDepth > 0) { + --FlowDepth; + continue; + } + if (Character == '#' && (Cursor == Begin || isAsciiSpace(Text[Cursor - 1]))) + return std::nullopt; + if (Character != ':' || FlowDepth != 0) + continue; + if (Cursor + 1 == End || isAsciiSpace(Text[Cursor + 1]) || Text[Cursor + 1] == '#') + return Cursor; + } + return std::nullopt; + } + + [[nodiscard]] bool isSequenceIndicator(std::size_t Offset, std::size_t End) const noexcept { + if (Offset >= End || Parsed.Source.text()[Offset] != '-') + return false; + return Offset + 1 == End || isAsciiSpace(Parsed.Source.text()[Offset + 1]) || + Parsed.Source.text()[Offset + 1] == '#'; + } + + void skipFlowSpace(std::size_t &Cursor, std::size_t End) const noexcept { + const std::string_view Text = Parsed.Source.text(); + while (Cursor < End) { + if (isAsciiSpace(Text[Cursor]) || Text[Cursor] == '\r' || Text[Cursor] == '\n') { + ++Cursor; + continue; + } + if (Text[Cursor] != '#') + return; + while (Cursor < End && Text[Cursor] != '\n') + ++Cursor; + } + } + + [[nodiscard]] bool isFlowBlockSequenceStart(std::size_t Cursor, std::size_t End) const noexcept { + if (Cursor >= End) + return false; + const std::size_t LineIndex = Parsed.Source.positionAt(Cursor).Line - 1; + if (LineIndex >= Lines.size()) + return false; + const PhysicalLine &Line = Lines[LineIndex]; + return Cursor == Line.Begin + Line.Indent && isSequenceIndicator(Cursor, Line.ContentEnd); + } + + [[nodiscard]] bool isWhitespaceOnlyLine(std::size_t LineIndex) const noexcept { + const PhysicalLine &Line = Lines[LineIndex]; + const std::string_view Text = Parsed.Source.text(); + for (std::size_t Cursor = Line.Begin; Cursor < Line.ContentEnd; ++Cursor) { + if (!isAsciiSpace(Text[Cursor])) + return false; + } + return true; + } + + [[nodiscard]] bool checkDepth(std::size_t Depth) { + if (Depth <= Options.MaximumDepth) + return true; + diagnose(0, 0, "maximum YAML nesting depth exceeded"); + return false; + } + + [[nodiscard]] NodeID addNullNode(std::size_t Begin, std::size_t End) { + return addNode(Node(NodeKind::Null, Parsed.Source.range(Begin, End))); + } + + [[nodiscard]] NodeID addScalarNode(std::size_t Begin, std::size_t End, ScalarStyle Style, + std::string Value) { + Node NewNode(NodeKind::Scalar, Parsed.Source.range(Begin, End)); + NewNode.ScalarStyleValue = Style; + NewNode.Scalar = std::move(Value); + return addNode(std::move(NewNode)); + } + + [[nodiscard]] NodeID addSequenceNode(std::size_t Begin, std::size_t End, CollectionStyle Style, + std::vector<NodeID> Items) { + Node NewNode(NodeKind::Sequence, Parsed.Source.range(Begin, End)); + NewNode.CollectionStyleValue = Style; + NewNode.Sequence = std::move(Items); + return addNode(std::move(NewNode)); + } + + [[nodiscard]] NodeID addMappingNode(std::size_t Begin, std::size_t End, CollectionStyle Style, + std::vector<MappingEntry> Entries) { + Node NewNode(NodeKind::Mapping, Parsed.Source.range(Begin, End)); + NewNode.CollectionStyleValue = Style; + NewNode.Mapping = std::move(Entries); + return addNode(std::move(NewNode)); + } + + [[nodiscard]] NodeID addNode(Node NewNode) { + if (Parsed.Nodes.size() >= Options.MaximumNodes) { + diagnose(NewNode.range().Begin.Offset, NewNode.range().End.Offset, + "maximum YAML node count exceeded"); + return {}; + } + const auto RawID = static_cast<std::uint32_t>(Parsed.Nodes.size()); + Parsed.Nodes.push_back(std::move(NewNode)); + return NodeID(RawID); + } + + Stream Parsed; + ParseOptions Options; + std::vector<PhysicalLine> Lines; + std::vector<TextDiagnostic> Diagnostics; + bool Failed = false; +}; + +ParseResult parse(std::string SourceName, std::string SourceText, ParseOptions Options) { + return Parser(std::move(SourceName), std::move(SourceText), Options).run(); +} + +} // namespace rcc::yaml |
