├── .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 | --------------------------------------------------------------------------------