Skip to content

Commit 198adc4

Browse files
committed
fixing DSL
1 parent 6f7d4dd commit 198adc4

File tree

7 files changed

+340
-19
lines changed

7 files changed

+340
-19
lines changed

Examples/Remaining/generics/dsl.swift

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,14 @@ let genericGroup = Group{
1313
PropertyRequirement("count", type: "Int", access: .get)
1414
}
1515

16-
Struct("Stack", generic: "Element") {
16+
Struct("Stack") {
1717
Variable(.var, name: "items", type: "[Element]", equals: "[]")
1818

19-
Function("push") parameters:{
19+
Function("push") {
2020
Parameter(name: "item", type: "Element")
21-
} {
22-
VariableExp("output").call("append") {
23-
Parameter(name: "item", value: "item")
21+
} _: {
22+
VariableExp("items").call("append") {
23+
ParameterExp(name: "item", value: "item")
2424
}
2525
}
2626

@@ -39,12 +39,12 @@ let genericGroup = Group{
3939
ComputedProperty("count") {
4040
VariableExp("items").property("count")
4141
}
42-
}
42+
}.generic("Element")
4343

4444
Enum("Noop") {
45-
Function("nothing", parameters: {
45+
Function("nothing", returns: "any Stackable") {
4646
Parameter(name: "stack", type: "any Stackable")
47-
}, returns: "any Stackable") {
47+
} _: {
4848
Returns{
4949
VariableExp("stack")
5050
}

Examples/Remaining/generics/syntax.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// AssociatedType.swift
2+
// SyntaxKit
3+
//
4+
// Created by Leo Dion.
5+
//
6+
// Permission is hereby granted, free of charge, to any person
7+
// obtaining a copy of this software and associated documentation
8+
// files (the “Software”), to deal in the Software without
9+
// restriction, including without limitation the rights to use,
10+
// copy, modify, merge, publish, distribute, sublicense, and/or
11+
// sell copies of the Software, and to permit persons to whom the
12+
// Software is furnished to do so, subject to the following
13+
// conditions:
14+
//
15+
// The above copyright notice and this permission notice shall be
16+
// included in all copies or substantial portions of the Software.
17+
//
18+
// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
19+
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
20+
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21+
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
22+
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
23+
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
24+
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
25+
// OTHER DEALINGS IN THE SOFTWARE.
26+
//
27+
28+
import SwiftSyntax
29+
30+
/// Represents an associatedtype requirement in a protocol.
31+
public struct AssociatedType: CodeBlock, Sendable {
32+
private let name: String
33+
private var inherited: [String] = []
34+
35+
public init(_ name: String) {
36+
self.name = name
37+
}
38+
39+
/// Adds inherited protocols/constraints to the associatedtype.
40+
public func inherits(_ protocols: String...) -> Self {
41+
var copy = self
42+
copy.inherited.append(contentsOf: protocols)
43+
return copy
44+
}
45+
46+
public var syntax: SyntaxProtocol {
47+
let associatedTypeKeyword = TokenSyntax.keyword(.associatedtype, trailingTrivia: .space)
48+
let identifier = TokenSyntax.identifier(name)
49+
var inheritanceClause: TypeInheritanceClauseSyntax? = nil
50+
if !inherited.isEmpty {
51+
let joined = inherited.joined(separator: " & ")
52+
let inheritedTypes = InheritedTypeListSyntax([
53+
InheritedTypeSyntax(type: TypeSyntax(IdentifierTypeSyntax(name: .identifier(joined))))
54+
])
55+
inheritanceClause = TypeInheritanceClauseSyntax(
56+
colon: .colonToken(leadingTrivia: .space, trailingTrivia: .space),
57+
inheritedTypes: inheritedTypes
58+
)
59+
}
60+
return AssociatedTypeDeclSyntax(
61+
attributes: AttributeListSyntax([]),
62+
modifiers: DeclModifierListSyntax([]),
63+
associatedtypeKeyword: associatedTypeKeyword,
64+
name: identifier,
65+
inheritanceClause: inheritanceClause,
66+
initializer: nil,
67+
genericWhereClause: nil
68+
)
69+
}
70+
}

Sources/SyntaxKit/Declarations/Struct.swift

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ import SwiftSyntax
3333
public struct Struct: CodeBlock, Sendable {
3434
private let name: String
3535
private let members: [CodeBlock]
36-
private var genericParameter: String?
36+
private var genericParameters: [String] = []
3737
private var inheritance: [String] = []
3838
private var attributes: [AttributeInfo] = []
3939
private var accessModifier: AccessModifier?
@@ -48,12 +48,21 @@ public struct Struct: CodeBlock, Sendable {
4848
self.members = try content()
4949
}
5050

51-
/// Sets the generic parameter for the struct.
52-
/// - Parameter generic: The generic parameter name.
53-
/// - Returns: A copy of the struct with the generic parameter set.
51+
/// Adds a generic parameter to the struct.
52+
/// - Parameter generic: The generic parameter name to add.
53+
/// - Returns: A copy of the struct with the generic parameter added.
5454
public func generic(_ generic: String) -> Self {
5555
var copy = self
56-
copy.genericParameter = generic
56+
copy.genericParameters.append(generic)
57+
return copy
58+
}
59+
60+
/// Adds multiple generic parameters to the struct.
61+
/// - Parameter generics: The list of generic parameter names to add.
62+
/// - Returns: A copy of the struct with the generic parameters added.
63+
public func generic(_ generics: String...) -> Self {
64+
var copy = self
65+
copy.genericParameters.append(contentsOf: generics)
5766
return copy
5867
}
5968

@@ -91,14 +100,22 @@ public struct Struct: CodeBlock, Sendable {
91100
let identifier = TokenSyntax.identifier(name)
92101

93102
var genericParameterClause: GenericParameterClauseSyntax?
94-
if let generic = genericParameter {
95-
let genericParameter = GenericParameterSyntax(
96-
name: .identifier(generic),
97-
trailingComma: nil
103+
if !genericParameters.isEmpty {
104+
let parameterList = GenericParameterListSyntax(
105+
genericParameters.enumerated().map { idx, name in
106+
var param = GenericParameterSyntax(name: .identifier(name))
107+
if idx < genericParameters.count - 1 {
108+
param = param.with(
109+
\.trailingComma,
110+
TokenSyntax.commaToken(trailingTrivia: .space)
111+
)
112+
}
113+
return param
114+
}
98115
)
99116
genericParameterClause = GenericParameterClauseSyntax(
100117
leftAngle: .leftAngleToken(),
101-
parameters: GenericParameterListSyntax([genericParameter]),
118+
parameters: parameterList,
102119
rightAngle: .rightAngleToken()
103120
)
104121
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import Foundation
2+
import SyntaxKit
3+
import Testing
4+
5+
@Suite internal struct GenericsExampleTests {
6+
@Test("Generics DSL generates expected Swift code (integration)")
7+
internal func testGenericsExampleIntegration() throws {
8+
// DSL equivalent of Examples/Remaining/generics/dsl.swift
9+
let program = Group {
10+
Protocol("Stackable") {
11+
AssociatedType("Element").inherits("Hashable", "Identifiable")
12+
FunctionRequirement("push") {
13+
Parameter(unlabeled: "item", type: "Element")
14+
}.mutating()
15+
FunctionRequirement("pop", returns: "Element?").mutating()
16+
FunctionRequirement("peek", returns: "Element?")
17+
PropertyRequirement("isEmpty", type: "Bool", access: .get)
18+
PropertyRequirement("count", type: "Int", access: .get)
19+
}
20+
21+
Struct("Stack") {
22+
Variable(.var, name: "items", type: "[Element]", equals: Literal.array([])).withExplicitType()
23+
24+
Function("push") {
25+
Parameter(unlabeled: "item", type: "Element")
26+
} _: {
27+
VariableExp("items").call("append") {
28+
ParameterExp(unlabeled: VariableExp("item"))
29+
}
30+
}.mutating()
31+
32+
Function("pop", returns: "Element?") {
33+
VariableExp("items").call("popLast")
34+
}.mutating()
35+
36+
Function("peek", returns: "Element?") {
37+
VariableExp("items").property("last")
38+
}
39+
40+
ComputedProperty("isEmpty", type: "Bool") {
41+
VariableExp("items").property("isEmpty")
42+
}
43+
44+
ComputedProperty("count", type: "Int") {
45+
VariableExp("items").property("count")
46+
}
47+
}.generic("Element")
48+
49+
Enum("Noop") {
50+
Function("nothing", returns: "any Stackable") {
51+
Parameter(unlabeled: "stack", type: "any Stackable")
52+
} _: {
53+
Return{
54+
VariableExp("stack")
55+
}
56+
}.static()
57+
}
58+
}
59+
60+
// Expected Swift code from Examples/Remaining/generics/code.swift
61+
let expectedCode = """
62+
protocol Stackable {
63+
associatedtype Element : Hashable & Identifiable
64+
65+
mutating func push(_ item: Element)
66+
mutating func pop() -> Element?
67+
func peek() -> Element?
68+
var isEmpty: Bool { get }
69+
var count: Int { get }
70+
}
71+
72+
struct Stack<Element> {
73+
var items: [Element] = []
74+
75+
mutating func push(_ item: Element) {
76+
items.append(item)
77+
}
78+
79+
mutating func pop() -> Element? {
80+
items.popLast()
81+
}
82+
83+
func peek() -> Element? {
84+
items.last
85+
}
86+
87+
var isEmpty: Bool {
88+
items.isEmpty
89+
}
90+
91+
var count: Int {
92+
items.count
93+
}
94+
}
95+
96+
enum Noop {
97+
static func nothing(_ stack: any Stackable) -> any Stackable {
98+
return stack
99+
}
100+
}
101+
"""
102+
103+
// Generate code from DSL
104+
let generated = program.generateCode().normalize()
105+
let expected = expectedCode.normalize()
106+
#expect(generated == expected)
107+
}
108+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import Foundation
2+
import SyntaxKit
3+
import Testing
4+
5+
@Suite internal struct GenericsTests {
6+
@Test("Generics DSL generates expected Swift code")
7+
internal func testGenericsExample() throws {
8+
// Build DSL equivalent of Examples/Remaining/generics/dsl.swift
9+
let program = try Group {
10+
Protocol("Stackable") {
11+
AssociatedType("Element").inherits("Hashable", "Identifiable")
12+
FunctionRequirement("push") {
13+
Parameter(name: "item", type: "Element")
14+
}.mutating()
15+
FunctionRequirement("pop", returns: "Element?").mutating()
16+
FunctionRequirement("peek", returns: "Element?")
17+
PropertyRequirement("isEmpty", type: "Bool", access: .get)
18+
PropertyRequirement("count", type: "Int", access: .get)
19+
}
20+
21+
Struct("Stack") {
22+
Variable(.var, name: "items", type: "[Element]", equals: "[]")
23+
24+
Function("push") {
25+
Parameter(name: "item", type: "Element")
26+
} _: {
27+
VariableExp("items").call("append") {
28+
ParameterExp(name: "item", value: "item")
29+
}
30+
}
31+
32+
Function("pop", returns: "Element?") {
33+
VariableExp("items").call("popLast")
34+
}
35+
36+
Function("peek", returns: "Element?") {
37+
VariableExp("items").property("last")
38+
}
39+
40+
ComputedProperty("isEmpty", type: "Bool") {
41+
VariableExp("items").property("isEmpty")
42+
}
43+
44+
ComputedProperty("count", type: "Int") {
45+
VariableExp("items").property("count")
46+
}
47+
}.generic("Element")
48+
49+
Enum("Noop") {
50+
Function("nothing", returns: "any Stackable") {
51+
Parameter(name: "stack", type: "any Stackable")
52+
} _: {
53+
Return{
54+
VariableExp("stack")
55+
}
56+
}
57+
}
58+
}
59+
60+
// For now, just test that it compiles
61+
let generated = program.generateCode()
62+
print("Generated code:")
63+
print(generated)
64+
65+
// TODO: Add proper comparison once AssociatedType is implemented
66+
#expect(!generated.isEmpty)
67+
}
68+
}

0 commit comments

Comments
 (0)