/SampleFunctionBuilder.swift Secret
Created
September 9, 2020 13:43
-
-
Save twostraws/f100939331eb87122149af7178ddde1a to your computer and use it in GitHub Desktop.
A function builder which prints out information about how function builders work.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SampleFunctionBuilder.swift - Function builder debugging aid | |
// | |
// This source file is part of the Swift.org open source project | |
// | |
// Copyright (c) 2020 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 | |
// | |
// ----------------------------------------------------------------------------- | |
/// | |
/// This file contains SampleFunctionBuilder, a function builder which records | |
/// the calls that were used to construct the result of the function, and | |
/// therefore can be used to learn how function builders work. | |
/// | |
// ----------------------------------------------------------------------------- | |
@_functionBuilder | |
public struct SampleFunctionBuilder { | |
public struct Call { | |
fileprivate var method: Method | |
fileprivate var location: SourceLoc | |
} | |
fileprivate indirect enum Method { | |
case buildExpression(Any) | |
case buildFinalResult(Call) | |
case buildLimitedAvailability(Call) | |
case buildEitherFirst(Call) | |
case buildEitherSecond(Call) | |
case buildOptional(Call?) | |
case buildArray([Call]) | |
case buildBlock([Call]) | |
case buildDo([Call]) | |
} | |
fileprivate struct SourceLoc: CustomStringConvertible { | |
let file: String | |
let line: Int | |
let column: Int | |
var description: String { "\(file):\(line):\(column)" } | |
} | |
public static func buildExpression(_ expression: Any, file: String = #file, line: Int = #line, column: Int = #column) -> Call { | |
.init(method: .buildExpression(expression), location: .init(file: file, line: line, column: column)) | |
} | |
public static func buildBlock(_ components: Call..., file: String = #file, line: Int = #line, column: Int = #column) -> Call { | |
.init(method: .buildBlock(components), location: .init(file: file, line: line, column: column)) | |
} | |
public static func buildFinalResult(_ component: Call, file: String = #file, line: Int = #line, column: Int = #column) -> Call { | |
.init(method: .buildFinalResult(component), location: .init(file: file, line: line, column: column)) | |
} | |
public static func buildDo(_ components: Call..., file: String = #file, line: Int = #line, column: Int = #column) -> Call { | |
.init(method: .buildDo(components), location: .init(file: file, line: line, column: column)) | |
} | |
public static func buildOptional(_ component: Call?, file: String = #file, line: Int = #line, column: Int = #column) -> Call { | |
.init(method: .buildOptional(component), location: .init(file: file, line: line, column: column)) | |
} | |
public static func buildEither(first component: Call, file: String = #file, line: Int = #line, column: Int = #column) -> Call { | |
.init(method: .buildEitherFirst(component), location: .init(file: file, line: line, column: column)) | |
} | |
public static func buildEither(second component: Call, file: String = #file, line: Int = #line, column: Int = #column) -> Call { | |
.init(method: .buildEitherSecond(component), location: .init(file: file, line: line, column: column)) | |
} | |
public static func buildArray(_ components: [Call], file: String = #file, line: Int = #line, column: Int = #column) -> Call { | |
.init(method: .buildArray(components), location: .init(file: file, line: line, column: column)) | |
} | |
public static func buildLimitedAvailability(_ component: Call, file: String = #file, line: Int = #line, column: Int = #column) -> Call { | |
.init(method: .buildLimitedAvailability(component), location: .init(file: file, line: line, column: column)) | |
} | |
public static func calls(@SampleFunctionBuilder in body: () -> Call) -> Call { | |
body() | |
} | |
} | |
fileprivate struct Line: CustomStringConvertible { | |
var level: Int = 0 | |
var text: Substring | |
func withAnotherLevel() -> Line { | |
var copy = self | |
copy.level += 1 | |
return copy | |
} | |
var description: String { | |
String(repeating: " ", count: level) + text | |
} | |
} | |
fileprivate extension Array where Element == Line { | |
init(from expression: Any) { | |
let str = String(reflecting: expression) | |
self = str.split(separator: "\n", omittingEmptySubsequences: false) | |
.map { Line(text: $0) } | |
} | |
func wrappedBy( | |
prefix: Substring, | |
suffix: Substring | |
) -> [Line] { | |
[ Line(text: prefix) ] | |
+ map { $0.withAnotherLevel() } | |
+ [ Line(text: suffix) ] | |
} | |
func mapFirstText(_ transform: (Substring) -> Substring) -> [Line] { | |
var copy = self | |
copy[0].text = transform(copy[0].text) | |
return copy | |
} | |
func mapLastText(_ transform: (Substring) -> Substring) -> [Line] { | |
var copy = self | |
copy[endIndex - 1].text = transform(copy[endIndex - 1].text) | |
return copy | |
} | |
func withLoc(_ loc: SampleFunctionBuilder.SourceLoc) -> [Line] { | |
[Line](from: loc).mapFirstText { "// \($0)" } + self | |
} | |
} | |
extension SampleFunctionBuilder.Method { | |
func debugDescriptionLines() -> [Line] { | |
switch self { | |
case .buildExpression(let expression): | |
return [Line](from: expression) | |
.wrappedBy( | |
prefix: "SampleFunctionBuilder.buildExpression(", | |
suffix: ")" | |
) | |
case .buildFinalResult(let component): | |
return component.debugDescriptionLines() | |
.wrappedBy( | |
prefix: "SampleFunctionBuilder.buildFinalResult(", | |
suffix: ")" | |
) | |
case .buildLimitedAvailability(let component): | |
return component.debugDescriptionLines() | |
.wrappedBy( | |
prefix: "SampleFunctionBuilder.buildLimitedAvailability(", | |
suffix: ")" | |
) | |
case .buildEitherFirst(let component): | |
return component.debugDescriptionLines(label: "first: ") | |
.wrappedBy( | |
prefix: "SampleFunctionBuilder.buildEither(", | |
suffix: ")" | |
) | |
case .buildEitherSecond(let component): | |
return component.debugDescriptionLines(label: "second: ") | |
.wrappedBy( | |
prefix: "SampleFunctionBuilder.buildEither(", | |
suffix: ")" | |
) | |
case .buildOptional(let component): | |
return component.debugDescriptionLines() | |
.wrappedBy( | |
prefix: "SampleFunctionBuilder.buildOptional(", | |
suffix: ")" | |
) | |
case .buildArray(let components): | |
return components.debugDescriptionLines() | |
.wrappedBy(prefix: "[", suffix: "]") | |
.wrappedBy( | |
prefix: "SampleFunctionBuilder.buildArray(", | |
suffix: ")" | |
) | |
case .buildBlock(let components): | |
return components.debugDescriptionLines() | |
.wrappedBy( | |
prefix: "SampleFunctionBuilder.buildBlock(", | |
suffix: ")" | |
) | |
case .buildDo(let components): | |
return components.debugDescriptionLines() | |
.wrappedBy( | |
prefix: "SampleFunctionBuilder.buildDo(", | |
suffix: ")" | |
) | |
} | |
} | |
} | |
fileprivate extension Array where Element == SampleFunctionBuilder.Call { | |
func debugDescriptionLines(label: String = "") -> [Line] { | |
let lineGroups = [first!.debugDescriptionLines(label: label)] + | |
dropFirst().map { $0.debugDescriptionLines() } | |
return lineGroups.dropLast().flatMap { $0.mapLastText { "\($0)," } } + | |
lineGroups.last! | |
} | |
} | |
fileprivate extension Optional where Wrapped == SampleFunctionBuilder.Call { | |
func debugDescriptionLines(label: String = "") -> [Line] { | |
map { $0.debugDescriptionLines(label: label) } ?? [ Line(text: label + "nil") ] | |
} | |
} | |
fileprivate extension SampleFunctionBuilder.Call { | |
func debugDescriptionLines(label: String = "") -> [Line] { | |
method | |
.debugDescriptionLines() | |
.mapFirstText { label + $0 } | |
.withLoc(location) | |
} | |
} | |
extension SampleFunctionBuilder.Call: CustomDebugStringConvertible { | |
public var debugDescription: String { | |
debugDescriptionLines().map(String.init(describing:)).joined(separator: "\n") | |
} | |
} | |
// | |
// Usage: | |
// | |
let calls = SampleFunctionBuilder.calls { | |
1 | |
1 + 1 | |
"hello" | |
if true { "branch" } | |
if false { "branch" } | |
if true { true } else { false } | |
if false { true } else { false } | |
if #available(macOS 9, *) { "past" } | |
if #available(macOS 10.16, *) { "present" } | |
if #available(macOS 9000, *) { "future" } | |
// for i in 0..<3 { i } | |
do { "do block" } | |
} | |
print(calls) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment