blob: af3ea426c38df1b26f271f56b3a84213340dfe21 [file] [log] [blame]
//===------------------- 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 "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)
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 .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 .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 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)
}
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<Target>(to target: inout Target)
where Target: TextOutputStream {
func printRepeated(_ character: String, count: Int) {
for _ in 0..<count { target.write(character) }
}
switch self {
case let .spaces(count): printRepeated(" ", count: count)
case let .tabs(count): printRepeated("\t", count: count)
case let .verticalTabs(count): printRepeated("\u{2B7F}", count: count)
case let .formfeeds(count): printRepeated("\u{240C}", count: count)
case let .newlines(count): printRepeated("\n", count: count)
case let .backticks(count): printRepeated("`", count: count)
case let .lineComment(text),
let .blockComment(text),
let .docLineComment(text),
let .docBlockComment(text):
target.write(text)
}
}
}
/// 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 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)]
}
}
/// 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)
}