[swift] Swift Sourcery를 활용한 DSL 중첩 개발 방법

소개

Swift는 강력한 언어이지만, 특히 DSL(Domain Specific Language)을 개발하는데 있어서는 몇 가지 제한 사항이 있습니다. 하지만, Swift Sourcery를 활용하면 이러한 제한 사항을 극복할 수 있습니다. Swift Sourcery는 코드 생성 도구로, 소스 코드를 분석하여 자동으로 코드를 생성하는 기능을 제공합니다. 이를 활용하여 DSL 중첩 개발을 보다 쉽게 할 수 있습니다.

DSL 중첩 개발 방법

  1. 먼저, DSL을 사용할 구조체나 클래스를 정의합니다. 이 구조체나 클래스는 DSL의 각 요소를 정의하는 메서드와 속성을 가지고 있습니다.
  2. 다음으로, DSL을 사용할 때마다 코드를 직접 작성하는 대신, Swift Sourcery를 이용하여 코드를 자동으로 생성하는 템플릿을 작성합니다. 이 템플릿은 “Sourcery”라는 독립적인 프로그램을 사용하여 생성됩니다.
  3. 템플릿을 사용하여 코드를 생성하고, 해당 코드를 프로젝트에 추가합니다.
  4. DSL을 사용하는 코드에서는 생성된 코드를 import하여 해당 DSL을 사용할 수 있습니다.

예시

다음은 SwiftUI를 위한 DSL을 개발하는 예시입니다.

struct DSL {
    
    // DSL 요소 정의
    private static var stackViews: [AnyView] = []
    
    // DSL 요소를 추가하는 메서드 정의
    static func addStackView(_ stackView: AnyView) {
        stackViews.append(stackView)
    }
    
    // DSL 요소를 사용하는 메서드 정의
    static func build() -> AnyView {
        let combinedStackView = /* Combine stackViews into a single view */
        return combinedStackView
    }
}

위의 예시처럼, DSL 요소를 추가하는 메서드를 정의하고, 해당 요소를 사용하는 메서드를 정의합니다. 이때, 외부에서 접근하지 못하도록 private으로 설정하여 은닉성을 유지합니다.

Swift Sourcery를 이용하여 코드를 자동으로 생성하기 위해, Sourcery 템플릿을 작성합니다. 이 템플릿은 DSL을 사용하는 코드를 분석하고, 필요한 코드를 자동으로 생성합니다.

적절한 템플릿 생성 후, Sourcery를 실행하고 템플릿을 실행하여 코드를 생성합니다. 생성된 코드는 프로젝트에 추가하여 DSL을 사용할 수 있습니다.

import SwiftUI

@resultBuilder
public struct DSLBuilder {
    
    public static func buildBlock<Content>(_ content: Content) -> Content {
        return content
    }
    
    public static func buildBlock<Content>(_ contents: Content...) -> Content {
        return content
    }
    
    public static func buildIf<Content>(_ content: Content?) -> Content? {
        return content
    }
    
    public static func buildEither<TrueContent, FalseContent>(first: TrueContent) -> [AnyView] {
        return [AnyView(first)]
    }
    
    public static func buildEither<TrueContent, FalseContent>(second: FalseContent) -> [AnyView] {
        return [AnyView(second)]
    }
}

@_functionBuilder
public struct DSLFunctionBuilder {
    
    public static func buildBlock<Element>(_ elements: Element...) -> [Element] {
        return elements
    }
    
    public static func buildBlock<Element>(_ elements: [Element]) -> [Element] {
        return elements
    }
    
    public static func buildOptional<Element>(_ element: Element?) -> [Element] {
        if let element = element {
            return [element]
        } else {
            return []
        }
    }
    
    public static func buildEither<Element>(first: Element) -> [Element] {
        return [first]
    }
    
    public static func buildEither<Element>(second: Element) -> [Element] {
        return [second]
    }
}

위의 예시에서 @resultBuilder@_functionBuilder는 DSL을 작성할 때 사용되는 애노테이션입니다. 이 애노테이션을 사용하여 DSL을 정의하고, 읽기 쉬운 방식으로 코드를 작성할 수 있습니다.

결론

Swift Sourcery를 활용하면, DSL 중첩 개발을 보다 쉽고 자유롭게 할 수 있습니다. Swift Sourcery를 활용하여 DSL 개발에 대한 제약 사항을 극복하고, 좀 더 효율적인 개발을 할 수 있습니다.