├── .gitignore
├── .swiftpm
└── xcode
│ └── xcshareddata
│ └── xcschemes
│ ├── RGBA.xcscheme
│ └── RGBATests.xcscheme
├── Package.resolved
├── Package.swift
├── README.md
├── Sources
├── RGBA
│ ├── Code
│ │ ├── RGBACode.swift
│ │ ├── RGBADynamicCode.swift
│ │ ├── RGBAOperator.swift
│ │ ├── RGBAType.swift
│ │ └── RGBAValue.swift
│ ├── RGBAMacros.swift
│ ├── RGBAProtocol.swift
│ ├── RGBAReturn.swift
│ └── RGBAVariable.swift
└── RGBAMacros
│ ├── Plugin.swift
│ ├── RGBAMacro.swift
│ └── RGBAShader.swift
└── Tests
└── RGBATests
├── RGBAMacroTests.swift
└── RGBATests.swift
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | xcuserdata/
5 | DerivedData/
6 | .swiftpm/configuration/registries.json
7 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
8 | .netrc
9 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/xcshareddata/xcschemes/RGBA.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
29 |
35 |
36 |
37 |
38 |
39 |
45 |
46 |
48 |
54 |
55 |
56 |
57 |
58 |
68 |
69 |
75 |
76 |
82 |
83 |
84 |
85 |
87 |
88 |
91 |
92 |
93 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/xcshareddata/xcschemes/RGBATests.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
16 |
18 |
24 |
25 |
26 |
27 |
28 |
38 |
39 |
45 |
46 |
48 |
49 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "pins" : [
3 | {
4 | "identity" : "swift-collections",
5 | "kind" : "remoteSourceControl",
6 | "location" : "https://github.com/apple/swift-collections",
7 | "state" : {
8 | "revision" : "937e904258d22af6e447a0b72c0bc67583ef64a2",
9 | "version" : "1.0.4"
10 | }
11 | },
12 | {
13 | "identity" : "swift-syntax",
14 | "kind" : "remoteSourceControl",
15 | "location" : "https://github.com/apple/swift-syntax",
16 | "state" : {
17 | "revision" : "165fc6d22394c1168ff76ab5d951245971ef07e5",
18 | "version" : "509.0.0-swift-DEVELOPMENT-SNAPSHOT-2023-06-05-a"
19 | }
20 | }
21 | ],
22 | "version" : 2
23 | }
24 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version: 5.9
2 |
3 | import PackageDescription
4 | import CompilerPluginSupport
5 |
6 | let package = Package(
7 | name: "RGBA",
8 | platforms: [
9 | .iOS(.v13),
10 | .tvOS(.v13),
11 | .macOS(.v10_15),
12 | ],
13 | products: [
14 | .library(
15 | name: "RGBA",
16 | targets: ["RGBA"]),
17 | ],
18 | dependencies: [
19 | .package(
20 | url: "https://github.com/apple/swift-syntax",
21 | from: "509.0.0-swift-5.9-DEVELOPMENT-SNAPSHOT-2023-04-25-b"
22 | ),
23 | .package(
24 | url: "https://github.com/apple/swift-collections",
25 | from: "1.0.0"
26 | ),
27 | ],
28 | targets: [
29 | .target(
30 | name: "RGBA",
31 | dependencies: [
32 | "RGBAMacros",
33 | .product(name: "Collections", package: "swift-collections"),
34 | ]),
35 | .testTarget(
36 | name: "RGBATests",
37 | dependencies: [
38 | "RGBA",
39 | .product(name: "SwiftSyntaxMacrosTestSupport",
40 | package: "swift-syntax"),
41 | ]),
42 | .macro(
43 | name: "RGBAMacros",
44 | dependencies: [
45 | .product(name: "SwiftSyntaxMacros",
46 | package: "swift-syntax"),
47 | .product(name: "SwiftCompilerPlugin",
48 | package: "swift-syntax"),
49 | ]),
50 | ]
51 | )
52 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # RGBA
2 |
3 | Write Metal in Swift
4 |
5 | ## Swift
6 |
7 | Using the `@RGBAShader` macro.
8 |
9 | ```swift
10 | @RGBAShader
11 | struct DemoShader {
12 |
13 | var a = 1.0
14 | var b = 2.0
15 |
16 | var code: RGBACode {
17 | $a + $b
18 | }
19 | }
20 | ```
21 |
22 | ## Metal
23 |
24 | Auto generated code.
25 |
26 | ```metal
27 | float a = 1.0;
28 | float b = 2.0;
29 | return a + b;
30 | ```
31 |
--------------------------------------------------------------------------------
/Sources/RGBA/Code/RGBACode.swift:
--------------------------------------------------------------------------------
1 |
2 | public protocol RGBACode {
3 | var text: String { get }
4 | }
5 |
--------------------------------------------------------------------------------
/Sources/RGBA/Code/RGBADynamicCode.swift:
--------------------------------------------------------------------------------
1 | import OrderedCollections
2 |
3 | public enum RGBADynamicCode: RGBACode {
4 |
5 | case variable(name: String, value: RGBAValue)
6 |
7 | indirect case combine(operator: RGBAOperator, lhs: RGBACode, rhs: RGBACode)
8 | }
9 |
10 | extension RGBADynamicCode {
11 |
12 | private var reference: String {
13 | switch self {
14 | case let .variable(name, _):
15 | return name
16 | case let .combine(`operator`, lhs, rhs):
17 | let leading: String = (lhs as? RGBADynamicCode)?.reference ?? lhs.text
18 | let trailing: String = (rhs as? RGBADynamicCode)?.reference ?? rhs.text
19 | return "\(leading) \(`operator`.rawValue) \(trailing)"
20 | }
21 | }
22 |
23 | public var text: String {
24 | var shader = ""
25 | for (name, value) in variables {
26 | shader += "\(value.type.rawValue) \(name) = \(value.text);\n"
27 | }
28 | shader += "return \(reference);"
29 | return shader
30 | }
31 | }
32 |
33 | extension RGBADynamicCode {
34 |
35 | var variables: OrderedDictionary {
36 | var variables: OrderedDictionary = [:]
37 | switch self {
38 | case .variable(let name, let value):
39 | variables[name] = value
40 | case .combine(_, let lhs, let rhs):
41 | if let dynamicCode = lhs as? RGBADynamicCode {
42 | for (name, value) in dynamicCode.variables {
43 | variables[name] = value
44 | }
45 | }
46 | if let dynamicCode = rhs as? RGBADynamicCode {
47 | for (name, value) in dynamicCode.variables {
48 | variables[name] = value
49 | }
50 | }
51 | }
52 | return variables
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/Sources/RGBA/Code/RGBAOperator.swift:
--------------------------------------------------------------------------------
1 | public enum RGBAOperator: String {
2 | case add = "+"
3 | case multiply = "*"
4 | }
5 |
6 | // MARK: Add
7 |
8 | extension RGBACode {
9 |
10 | public static func + (lhs: Self, rhs: Self) -> RGBADynamicCode {
11 | .combine(operator: .add, lhs: lhs, rhs: rhs)
12 | }
13 | }
14 |
15 |
16 | extension RGBACode {
17 |
18 | public static func + (lhs: Self, rhs: RGBADynamicCode) -> RGBADynamicCode {
19 | .combine(operator: .add, lhs: lhs, rhs: rhs)
20 | }
21 | }
22 |
23 |
24 | extension RGBACode {
25 |
26 | public static func + (lhs: RGBADynamicCode, rhs: Self) -> RGBADynamicCode {
27 | .combine(operator: .add, lhs: lhs, rhs: rhs)
28 | }
29 | }
30 |
31 |
32 | extension RGBADynamicCode {
33 |
34 | public static func + (lhs: Self, rhs: Self) -> Self {
35 | .combine(operator: .add, lhs: lhs, rhs: rhs)
36 | }
37 | }
38 |
39 | // MARK: Multiply
40 |
41 | extension RGBACode {
42 |
43 | public static func * (lhs: Self, rhs: Self) -> RGBADynamicCode {
44 | .combine(operator: .multiply, lhs: lhs, rhs: rhs)
45 | }
46 | }
47 |
48 |
49 | extension RGBACode {
50 |
51 | public static func * (lhs: Self, rhs: RGBADynamicCode) -> RGBADynamicCode {
52 | .combine(operator: .multiply, lhs: lhs, rhs: rhs)
53 | }
54 | }
55 |
56 |
57 | extension RGBACode {
58 |
59 | public static func * (lhs: RGBADynamicCode, rhs: Self) -> RGBADynamicCode {
60 | .combine(operator: .multiply, lhs: lhs, rhs: rhs)
61 | }
62 | }
63 |
64 |
65 | extension RGBADynamicCode {
66 |
67 | public static func * (lhs: Self, rhs: Self) -> Self {
68 | .combine(operator: .multiply, lhs: lhs, rhs: rhs)
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/Sources/RGBA/Code/RGBAType.swift:
--------------------------------------------------------------------------------
1 | public enum RGBAType: String {
2 | case float
3 | }
4 |
--------------------------------------------------------------------------------
/Sources/RGBA/Code/RGBAValue.swift:
--------------------------------------------------------------------------------
1 | import CoreGraphics
2 |
3 | public protocol RGBAValue: RGBACode {
4 | var type: RGBAType { get }
5 | }
6 |
7 | extension Float: RGBAValue {
8 | public var type: RGBAType { .float }
9 | public var text: String { "\(self)" }
10 | }
11 |
12 | extension Double: RGBAValue {
13 | public var type: RGBAType { .float }
14 | public var text: String { "\(self)" }
15 | }
16 |
17 | extension CGFloat: RGBAValue {
18 | public var type: RGBAType { .float }
19 | public var text: String { "\(self)" }
20 | }
21 |
--------------------------------------------------------------------------------
/Sources/RGBA/RGBAMacros.swift:
--------------------------------------------------------------------------------
1 | import RGBAMacros
2 |
3 | @freestanding(expression)
4 | public macro stringify(_ value: T) -> (T, String) = #externalMacro(module: "RGBAMacros", type: "RGBAMacro")
5 |
6 | @attached(conformance)
7 | @attached(memberAttribute)
8 | public macro RGBAShader() = #externalMacro(module: "RGBAMacros", type: "RGBAShader")
9 |
--------------------------------------------------------------------------------
/Sources/RGBA/RGBAProtocol.swift:
--------------------------------------------------------------------------------
1 |
2 | public protocol RGBAProtocol {
3 | var code: RGBACode { get }
4 | }
5 |
--------------------------------------------------------------------------------
/Sources/RGBA/RGBAReturn.swift:
--------------------------------------------------------------------------------
1 |
2 | public struct RGBAReturn: RGBACode {
3 |
4 | let name: String
5 | let value: T
6 |
7 | public var text: String {
8 | "\(name)(\(value.text))"
9 | }
10 |
11 | public init(_ value: T, name: String) {
12 | self.name = name
13 | self.value = value
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Sources/RGBA/RGBAVariable.swift:
--------------------------------------------------------------------------------
1 |
2 | @propertyWrapper
3 | public struct RGBAVariable {
4 |
5 | let name: String
6 |
7 | public var wrappedValue: T
8 |
9 | public var projectedValue: RGBADynamicCode {
10 | .variable(name: name, value: wrappedValue)
11 | }
12 |
13 | public init(wrappedValue: T, name: String) {
14 | self.wrappedValue = wrappedValue
15 | self.name = name
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Sources/RGBAMacros/Plugin.swift:
--------------------------------------------------------------------------------
1 | import SwiftCompilerPlugin
2 | import SwiftSyntaxMacros
3 |
4 | @main
5 | struct RGBAMacroPlugin: CompilerPlugin {
6 | let providingMacros: [Macro.Type] = [
7 | RGBAMacro.self,
8 | RGBAShader.self,
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/Sources/RGBAMacros/RGBAMacro.swift:
--------------------------------------------------------------------------------
1 | import SwiftCompilerPlugin
2 | import SwiftSyntax
3 | import SwiftSyntaxBuilder
4 | import SwiftSyntaxMacros
5 |
6 | public struct RGBAMacro: ExpressionMacro {
7 | public static func expansion(
8 | of node: some FreestandingMacroExpansionSyntax,
9 | in context: some MacroExpansionContext
10 | ) -> ExprSyntax {
11 | guard let argument = node.argumentList.first?.expression else {
12 | fatalError("compiler bug: the macro does not have any arguments")
13 | }
14 |
15 | return "(\(argument), \(literal: argument.description))"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Sources/RGBAMacros/RGBAShader.swift:
--------------------------------------------------------------------------------
1 | import SwiftCompilerPlugin
2 | import SwiftSyntax
3 | import SwiftSyntaxBuilder
4 | import SwiftSyntaxMacros
5 |
6 | public struct RGBAShader: MemberAttributeMacro, ConformanceMacro {
7 |
8 | public static func expansion(
9 | of node: SwiftSyntax.AttributeSyntax,
10 | providingConformancesOf declaration: Declaration,
11 | in context: Context
12 | ) throws -> [(
13 | SwiftSyntax.TypeSyntax,
14 | SwiftSyntax.GenericWhereClauseSyntax?
15 | )] where Declaration : SwiftSyntax.DeclGroupSyntax, Context : SwiftSyntaxMacros.MacroExpansionContext {
16 |
17 | [(SwiftSyntax.TypeSyntax.init(stringLiteral: "RGBAProtocol"), nil)]
18 | }
19 |
20 | public static func expansion(
21 | of node: SwiftSyntax.AttributeSyntax,
22 | attachedTo declaration: Declaration,
23 | providingAttributesFor member: MemberDeclaration,
24 | in context: Context
25 | ) throws -> [SwiftSyntax.AttributeSyntax] where Declaration : SwiftSyntax.DeclGroupSyntax, MemberDeclaration : SwiftSyntax.DeclSyntaxProtocol, Context : SwiftSyntaxMacros.MacroExpansionContext {
26 |
27 | // return [.init(stringLiteral: "\(member.debugDescription)")]
28 |
29 | // let allVariableNames: [String] = declaration.memberBlock.members.compactMap({ $0.decl.as(VariableDeclSyntax.self)?.bindings.first?.pattern.as(IdentifierPatternSyntax.self)?.identifier.text })
30 |
31 | if let variableName: String = member.as(VariableDeclSyntax.self)?.bindings.first?.pattern.as(IdentifierPatternSyntax.self)?.identifier.text {
32 |
33 | // let isRoot: Bool = allVariableNames.contains(variableName)
34 |
35 | if variableName == "code" {
36 | return []
37 | }
38 |
39 | return [.init(stringLiteral: "@RGBAVariable(name: \"\(variableName)\")")]
40 |
41 | } else if let functionName = member.as(FunctionDeclSyntax.self)?.identifier.text {
42 |
43 | return [] // [.init(stringLiteral: "@RGBAFunction(\"\(functionName)\")")]
44 | }
45 |
46 | return []
47 | }
48 | }
49 |
50 | //FunctionDeclSyntax
51 | //├─funcKeyword: keyword(SwiftSyntax.Keyword.func)
52 | //├─identifier: identifier(" xyz")
53 | //├─signature: FunctionSignatureSyntax
54 | //│ ├─input: ParameterClauseSyntax
55 | //│ │ ├─leftParen: leftParen
56 | //│ │ ├─parameterList: FunctionParameterListSyntax
57 | //│ │ │ ├─[0]: FunctionParameterSyntax
58 | //│ │ │ │ ├─firstName: identifier(" x")
59 | //│ │ │ │ ├─colon: colon
60 | //│ │ │ │ ├─type: SimpleTypeIdentifierSyntax
61 | //│ │ │ │ │ ╰─name: identifier(" CGFloat")
62 | //│ │ │ │ ╰─trailingComma: comma
63 | //│ │ │ ╰─[1]: FunctionParameterSyntax
64 | //│ │ │ ├─firstName: identifier(" y")
65 | //│ │ │ ├─colon: colon
66 | //│ │ │ ╰─type: SimpleTypeIdentifierSyntax
67 | //│ │ │ ╰─name: identifier(" CGFloat")
68 | //│ │ ╰─rightParen: rightParen
69 | //│ ╰─output: ReturnClauseSyntax
70 | //│ ├─arrow: arrow
71 | //│ ╰─returnType: SimpleTypeIdentifierSyntax
72 | //│ ╰─name: identifier(" CGFloat")
73 | //╰─body: CodeBlockSyntax
74 | // ├─leftBrace: leftBrace
75 | // ├─statements: CodeBlockItemListSyntax
76 | // │ ╰─[0]: CodeBlockItemSyntax
77 | // │ ╰─item: SequenceExprSyntax
78 | // │ ╰─elements: ExprListSyntax
79 | // │ ├─[0]: IdentifierExprSyntax
80 | // │ │ ╰─identifier: identifier(" x")
81 | // │ ├─[1]: BinaryOperatorExprSyntax
82 | // │ │ ╰─operatorToken: binaryOperator(" *")
83 | // │ ╰─[2]: IdentifierExprSyntax
84 | // │ ╰─identifier: identifier(" y")
85 | // ╰─rightBrace: rightBrace
86 |
--------------------------------------------------------------------------------
/Tests/RGBATests/RGBAMacroTests.swift:
--------------------------------------------------------------------------------
1 | //import SwiftSyntaxMacros
2 | //import SwiftSyntaxMacrosTestSupport
3 | //import XCTest
4 | //import RGBAMacro
5 | //import RGBA
6 | //
7 | //let testMacros: [String: Macro.Type] = [
8 | // "stringify": RGBAMacro.self,
9 | //]
10 | //
11 | //final class MyMacroTests: XCTestCase {
12 | //
13 | // func testMacro() {
14 | // assertMacroExpansion(
15 | // """
16 | // #stringify(a + b)
17 | // """,
18 | // expandedSource: """
19 | // (a + b, "a + b")
20 | // """,
21 | // macros: testMacros
22 | // )
23 | // }
24 | //
25 | // func testMacroWithStringLiteral() {
26 | // assertMacroExpansion(
27 | // #"""
28 | // #stringify("Hello, \(name)")
29 | // """#,
30 | // expandedSource: #"""
31 | // ("Hello, \(name)", #""Hello, \(name)""#)
32 | // """#,
33 | // macros: testMacros
34 | // )
35 | // }
36 | //}
37 |
--------------------------------------------------------------------------------
/Tests/RGBATests/RGBATests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import RGBA
3 |
4 | @RGBAShader
5 | struct TestShader {
6 |
7 | var a = 1.0
8 | var b = 2.0
9 |
10 | var code: RGBACode {
11 | $a + $b + 3.0
12 | }
13 | }
14 |
15 | final class RGBATests: XCTestCase {
16 |
17 | func testAdd() throws {
18 | let shader: RGBAProtocol = TestShader()
19 | XCTAssertEqual(
20 | shader.code.text,
21 | """
22 | float a = 1.0;
23 | float b = 2.0;
24 | return a + b + 3.0;
25 | """
26 | )
27 | }
28 | }
29 |
30 |
--------------------------------------------------------------------------------