//===------------------- Trivia.swift - Source Trivia Enum ----------------===// // // 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 // //===----------------------------------------------------------------------===// import Foundation /// A contiguous stretch of a single kind of trivia. The constituent part of /// a `Trivia` collection. /// /// For example, four spaces would be represented by /// `.spaces(4)` /// /// In general, you should deal with the actual Trivia collection instead /// of individual pieces whenever possible. public enum TriviaPiece: Codable { enum CodingKeys: CodingKey { case kind, value } public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) let kind = try container.decode(String.self, forKey: .kind) switch kind { case "Space": let value = try container.decode(Int.self, forKey: .value) self = .spaces(value) case "Tab": let value = try container.decode(Int.self, forKey: .value) self = .tabs(value) case "VerticalTab": let value = try container.decode(Int.self, forKey: .value) self = .verticalTabs(value) case "Formfeed": let value = try container.decode(Int.self, forKey: .value) self = .formfeeds(value) case "Newline": let value = try container.decode(Int.self, forKey: .value) self = .newlines(value) case "CarriageReturn": let value = try container.decode(Int.self, forKey: .value) self = .carriageReturns(value) case "Backtick": let value = try container.decode(Int.self, forKey: .value) self = .backticks(value) case "LineComment": let value = try container.decode(String.self, forKey: .value) self = .lineComment(value) case "BlockComment": let value = try container.decode(String.self, forKey: .value) self = .blockComment(value) case "DocLineComment": let value = try container.decode(String.self, forKey: .value) self = .docLineComment(value) case "DocBlockComment": let value = try container.decode(String.self, forKey: .value) self = .docLineComment(value) case "GarbageText": let value = try container.decode(String.self, forKey: .value) self = .garbageText(value) default: let context = DecodingError.Context(codingPath: [CodingKeys.kind], debugDescription: "invalid TriviaPiece kind \(kind)") throw DecodingError.valueNotFound(String.self, context) } } public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) switch self { case .blockComment(let comment): try container.encode("BlockComment", forKey: .kind) try container.encode(comment, forKey: .value) case .docBlockComment(let comment): try container.encode("DocBlockComment", forKey: .kind) try container.encode(comment, forKey: .value) case .docLineComment(let comment): try container.encode("DocLineComment", forKey: .kind) try container.encode(comment, forKey: .value) case .lineComment(let comment): try container.encode("LineComment", forKey: .kind) try container.encode(comment, forKey: .value) case .garbageText(let text): try container.encode("GarbageText", forKey: .kind) try container.encode(text, forKey: .value) case .formfeeds(let count): try container.encode("Formfeed", forKey: .kind) try container.encode(count, forKey: .value) case .backticks(let count): try container.encode("Backtick", forKey: .kind) try container.encode(count, forKey: .value) case .newlines(let count): try container.encode("Newline", forKey: .kind) try container.encode(count, forKey: .value) case .carriageReturns(let count): try container.encode("CarriageReturn", forKey: .kind) try container.encode(count, forKey: .value) case .spaces(let count): try container.encode("Space", forKey: .kind) try container.encode(count, forKey: .value) case .tabs(let count): try container.encode("Tab", forKey: .kind) try container.encode(count, forKey: .value) case .verticalTabs(let count): try container.encode("VerticalTab", forKey: .kind) try container.encode(count, forKey: .value) } } /// A space ' ' character. case spaces(Int) /// A tab '\t' character. case tabs(Int) /// A vertical tab '\v' character. case verticalTabs(Int) /// A form-feed '\f' character. case formfeeds(Int) /// A newline '\n' character. case newlines(Int) /// A carriage-return '\r' character. case carriageReturns(Int) /// A backtick '`' character, used to escape identifiers. case backticks(Int) /// A developer line comment, starting with '//' case lineComment(String) /// A developer block comment, starting with '/*' and ending with '*/'. case blockComment(String) /// A documentation line comment, starting with '///'. case docLineComment(String) /// A documentation block comment, starting with '/**' and ending with '*/. case docBlockComment(String) /// Any skipped text. case garbageText(String) } extension TriviaPiece: TextOutputStreamable { /// Prints the provided trivia as they would be written in a source file. /// /// - Parameter stream: The stream to which to print the trivia. public func write(to target: inout Target) where Target: TextOutputStream { func printRepeated(_ character: String, count: Int) { for _ in 0.. (lines: Int, lastColumn: Int, utf8Length: Int) { switch self { case .spaces(let n), .tabs(let n), .verticalTabs(let n), .formfeeds(let n), .backticks(let n): return (lines: 0, lastColumn: n, utf8Length: n) case .newlines(let n): return (lines: n, lastColumn: 0, utf8Length: n) case .carriageReturns(let n): return (lines: n, lastColumn: 0, utf8Length: n) case .lineComment(let text), .docLineComment(let text): let length = text.utf8.count return (lines: 0, lastColumn: length, utf8Length: length) case .blockComment(let text), .docBlockComment(let text), .garbageText(let text): var lines = 0 var col = 0 var total = 0 for char in text.utf8 { total += 1 if char == 0x0a /* ASCII newline */ || char == 0x0d /* ASCII carriage-return */{ col = 0 lines += 1 } else { col += 1 } } return (lines: lines, lastColumn: col, utf8Length: total) } } } /// A collection of leading or trailing trivia. This is the main data structure /// for thinking about trivia. public struct Trivia: Codable { let pieces: [TriviaPiece] /// Creates Trivia with the provided underlying pieces. public init(pieces: [TriviaPiece]) { self.pieces = pieces } public init(from decoder: Decoder) throws { var container = try decoder.unkeyedContainer() var pieces = [TriviaPiece]() while let piece = try container.decodeIfPresent(TriviaPiece.self) { pieces.append(piece) } self.pieces = pieces } public func encode(to encoder: Encoder) throws { var container = encoder.unkeyedContainer() for piece in pieces { try container.encode(piece) } } /// Creates Trivia with no pieces. public static var zero: Trivia { return Trivia(pieces: []) } /// Creates a new `Trivia` by appending the provided `TriviaPiece` to the end. public func appending(_ piece: TriviaPiece) -> Trivia { var copy = pieces copy.append(piece) return Trivia(pieces: copy) } /// Return a piece of trivia for some number of space characters in a row. public static func spaces(_ count: Int) -> Trivia { return [.spaces(count)] } /// Return a piece of trivia for some number of tab characters in a row. public static func tabs(_ count: Int) -> Trivia { return [.tabs(count)] } /// A vertical tab '\v' character. public static func verticalTabs(_ count: Int) -> Trivia { return [.verticalTabs(count)] } /// A form-feed '\f' character. public static func formfeeds(_ count: Int) -> Trivia { return [.formfeeds(count)] } /// Return a piece of trivia for some number of newline characters /// in a row. public static func newlines(_ count: Int) -> Trivia { return [.newlines(count)] } /// Return a piece of trivia for some number of carriage-return characters /// in a row. public static func carriageReturns(_ count: Int) -> Trivia { return [.carriageReturns(count)] } /// Return a piece of trivia for some number of backtick '`' characters /// in a row. public static func backticks(_ count: Int) -> Trivia { return [.backticks(count)] } /// Return a piece of trivia for a single line of ('//') developer comment. public static func lineComment(_ text: String) -> Trivia { return [.lineComment(text)] } /// Return a piece of trivia for a block comment ('/* ... */') public static func blockComment(_ text: String) -> Trivia { return [.blockComment(text)] } /// Return a piece of trivia for a single line of ('///') doc comment. public static func docLineComment(_ text: String) -> Trivia { return [.docLineComment(text)] } /// Return a piece of trivia for a documentation block comment ('/** ... */') public static func docBlockComment(_ text: String) -> Trivia { return [.docBlockComment(text)] } /// Return a piece of trivia for any garbage text. public static func garbageText(_ text: String) -> Trivia { return [.garbageText(text)] } /// Computes the total sizes and offsets of all pieces in this Trivia. func characterSizes() -> (lines: Int, lastColumn: Int, utf8Length: Int) { var lines = 0 var lastColumn = 0 var length = 0 for piece in pieces { let (ln, col, len) = piece.characterSizes() lines += ln lastColumn = col length += len } return (lines: lines, lastColumn: lastColumn, utf8Length: length) } } /// Conformance for Trivia to the Collection protocol. extension Trivia: Collection { public var startIndex: Int { return pieces.startIndex } public var endIndex: Int { return pieces.endIndex } public func index(after i: Int) -> Int { return pieces.index(after: i) } public subscript(_ index: Int) -> TriviaPiece { return pieces[index] } } extension Trivia: ExpressibleByArrayLiteral { /// Creates Trivia from the provided pieces. public init(arrayLiteral elements: TriviaPiece...) { self.pieces = elements } } /// Concatenates two collections of `Trivia` into one collection. public func +(lhs: Trivia, rhs: Trivia) -> Trivia { return Trivia(pieces: lhs.pieces + rhs.pieces) }