Skip to content

Instantly share code, notes, and snippets.

@twostraws
Forked from beccadax/SampleFunctionBuilder.swift
Created September 9, 2020 13:43
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save twostraws/f100939331eb87122149af7178ddde1a to your computer and use it in GitHub Desktop.
Save twostraws/f100939331eb87122149af7178ddde1a to your computer and use it in GitHub Desktop.
A function builder which prints out information about how function builders work.
// 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