| //===--- LineList.cpp - Data structures for Markup parsing ----------------===// |
| // |
| // This source file is part of the Swift.org open source project |
| // |
| // Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors |
| // Licensed under Apache License v2.0 with Runtime Library Exception |
| // |
| // See https://swift.org/LICENSE.txt for license information |
| // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "swift/AST/RawComment.h" |
| #include "swift/Basic/PrimitiveParsing.h" |
| #include "swift/Markup/LineList.h" |
| #include "swift/Markup/Markup.h" |
| |
| using namespace swift; |
| using namespace markup; |
| |
| std::string LineList::str() const { |
| std::string Result; |
| llvm::raw_string_ostream Stream(Result); |
| if (Lines.empty()) |
| return ""; |
| |
| auto FirstLine = Lines.begin(); |
| while (FirstLine != Lines.end() && FirstLine->Text.empty()) |
| ++FirstLine; |
| |
| if (FirstLine == Lines.end()) |
| return ""; |
| |
| auto InitialIndentation = measureIndentation(FirstLine->Text); |
| |
| for (auto Line = FirstLine; Line != Lines.end(); ++Line) { |
| auto Drop = std::min(InitialIndentation, Line->FirstNonspaceOffset); |
| Stream << Line->Text.drop_front(Drop) << "\n"; |
| } |
| |
| Stream.flush(); |
| return Result; |
| } |
| |
| size_t swift::markup::measureIndentation(StringRef Text) { |
| size_t Col = 0; |
| for (size_t i = 0, e = Text.size(); i != e; ++i) { |
| if (Text[i] == ' ' || Text[i] == '\v' || Text[i] == '\f') { |
| Col++; |
| continue; |
| } |
| |
| if (Text[i] == '\t') { |
| Col += ((i + 8) / 8) * 8; |
| continue; |
| } |
| return i; |
| } |
| return Text.size(); |
| } |
| |
| void LineListBuilder::addLine(llvm::StringRef Text, swift::SourceRange Range) { |
| Lines.push_back({Text, Range}); |
| } |
| |
| LineList LineListBuilder::takeLineList() const { |
| return LineList(Context.allocateCopy(ArrayRef<Line>(Lines))); |
| } |
| |
| static unsigned measureASCIIArt(StringRef S, unsigned NumLeadingSpaces) { |
| StringRef Spaces = S.substr(0, NumLeadingSpaces); |
| if (Spaces.size() != NumLeadingSpaces) |
| return 0; |
| if (Spaces.find_first_not_of(' ') != StringRef::npos) |
| return 0; |
| |
| S = S.drop_front(NumLeadingSpaces); |
| |
| if (S.startswith(" * ")) |
| return NumLeadingSpaces + 3; |
| if (S.startswith(" *\n") || S.startswith(" *\n\r")) |
| return NumLeadingSpaces + 2; |
| return 0; |
| } |
| |
| LineList MarkupContext::getLineList(swift::RawComment RC) { |
| LineListBuilder Builder(*this); |
| |
| for (const auto &C : RC.Comments) { |
| if (C.isLine()) { |
| // Skip comment marker. |
| unsigned CommentMarkerBytes = 2 + (C.isOrdinary() ? 0 : 1); |
| StringRef Cleaned = C.RawText.drop_front(CommentMarkerBytes); |
| |
| // Drop trailing newline. |
| Cleaned = Cleaned.rtrim("\n\r"); |
| auto CleanedStartLoc = |
| C.Range.getStart().getAdvancedLocOrInvalid(CommentMarkerBytes); |
| auto CleanedEndLoc = |
| C.Range.getStart().getAdvancedLocOrInvalid(Cleaned.size()); |
| Builder.addLine(Cleaned, { CleanedStartLoc, CleanedEndLoc }); |
| } else { |
| // Skip comment markers at the beginning and at the end. |
| unsigned CommentMarkerBytes = 2 + (C.isOrdinary() ? 0 : 1); |
| StringRef Cleaned = C.RawText.drop_front(CommentMarkerBytes); |
| |
| if (Cleaned.endswith("*/")) |
| Cleaned = Cleaned.drop_back(2); |
| else if (Cleaned.endswith("/")) |
| Cleaned = Cleaned.drop_back(1); |
| |
| swift::SourceLoc CleanedStartLoc = |
| C.Range.getStart().getAdvancedLocOrInvalid(CommentMarkerBytes); |
| |
| // Determine if we have leading decorations in this block comment. |
| bool HasASCIIArt = false; |
| if (swift::startsWithNewline(Cleaned)) { |
| Builder.addLine(Cleaned.substr(0, 0), { C.Range.getStart(), |
| C.Range.getStart() }); |
| unsigned NewlineBytes = swift::measureNewline(Cleaned); |
| Cleaned = Cleaned.drop_front(NewlineBytes); |
| CleanedStartLoc = CleanedStartLoc.getAdvancedLocOrInvalid(NewlineBytes); |
| HasASCIIArt = measureASCIIArt(Cleaned, C.StartColumn - 1) != 0; |
| } |
| |
| while (!Cleaned.empty()) { |
| size_t Pos = Cleaned.find_first_of("\n\r"); |
| if (Pos == StringRef::npos) |
| Pos = Cleaned.size(); |
| |
| // Skip over ASCII art, if present. |
| if (HasASCIIArt) |
| if (unsigned ASCIIArtBytes = |
| measureASCIIArt(Cleaned, C.StartColumn - 1)) { |
| Cleaned = Cleaned.drop_front(ASCIIArtBytes); |
| CleanedStartLoc = |
| CleanedStartLoc.getAdvancedLocOrInvalid(ASCIIArtBytes); |
| Pos -= ASCIIArtBytes; |
| } |
| |
| StringRef Line = Cleaned.substr(0, Pos); |
| auto CleanedEndLoc = CleanedStartLoc.getAdvancedLocOrInvalid(Pos); |
| |
| Cleaned = Cleaned.drop_front(Pos); |
| unsigned NewlineBytes = swift::measureNewline(Cleaned); |
| Cleaned = Cleaned.drop_front(NewlineBytes); |
| Pos += NewlineBytes; |
| CleanedStartLoc = CleanedStartLoc.getAdvancedLocOrInvalid(Pos); |
| |
| Builder.addLine(Line, { CleanedStartLoc, CleanedEndLoc }); |
| } |
| } |
| } |
| return Builder.takeLineList(); |
| } |
| |
| LineList LineList::subListWithRange(MarkupContext &MC, size_t StartLine, |
| size_t EndLine, size_t StartColumn, |
| size_t EndColumn) const { |
| auto TrimmedLines = ArrayRef<Line>(Lines.begin() + StartLine, |
| Lines.begin() + EndLine); |
| |
| LineListBuilder Builder(MC); |
| if (TrimmedLines.empty()) |
| return LineList(); |
| |
| auto FirstLine = TrimmedLines.begin(); |
| auto End = TrimmedLines.end(); |
| auto LastLine = End - 1; |
| |
| for (auto Line = FirstLine; Line != End; ++Line) { |
| auto T = Line->Text; |
| auto RangeStart = Line->Range.Start; |
| auto RangeEnd = Line->Range.End; |
| |
| if (Line == LastLine) { |
| T = T.drop_back(T.size() - EndColumn); |
| RangeEnd = RangeStart.getAdvancedLocOrInvalid(EndColumn); |
| } |
| |
| if (Line == FirstLine) { |
| T = T.drop_front(StartColumn); |
| RangeStart = RangeStart.getAdvancedLocOrInvalid(StartColumn); |
| } |
| |
| Builder.addLine(T, {RangeStart, RangeEnd}); |
| } |
| |
| return Builder.takeLineList(); |
| } |