Skip to content

Commit bfd791d

Browse files
committed
Improve printing of C types with better spacing and proper parentheses
1 parent c6066af commit bfd791d

File tree

4 files changed

+147
-44
lines changed

4 files changed

+147
-44
lines changed

Sources/JExtractSwift/CTypes/CFunction.swift

+6-4
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,9 @@ extension CFunction: CustomStringConvertible {
4848
public var description: String {
4949
var result = ""
5050

51-
resultType.printBefore(result: &result)
51+
var hasEmptyPlaceholder = false
52+
resultType.printBefore(hasEmptyPlaceholder: &hasEmptyPlaceholder, result: &result)
5253

53-
// FIXME: parentheses when needed.
54-
result += " "
5554
result += name
5655

5756
// Function parameters.
@@ -64,7 +63,10 @@ extension CFunction: CustomStringConvertible {
6463
)
6564
result += ")"
6665

67-
resultType.printAfter(result: &result)
66+
resultType.printAfter(
67+
hasEmptyPlaceholder: &hasEmptyPlaceholder,
68+
result: &result
69+
)
6870

6971
result += ""
7072
return result

Sources/JExtractSwift/CTypes/CType.swift

+97-18
Original file line numberDiff line numberDiff line change
@@ -74,19 +74,40 @@ public enum CType {
7474
extension CType: CustomStringConvertible {
7575
/// Print the part of this type that comes before the declarator, appending
7676
/// it to the provided `result` string.
77-
func printBefore(result: inout String) {
77+
func printBefore(hasEmptyPlaceholder: inout Bool, result: inout String) {
78+
// Save the value of hasEmptyPlaceholder and restore it once we're done
79+
// here.
80+
let previousHasEmptyPlaceholder = hasEmptyPlaceholder
81+
defer {
82+
hasEmptyPlaceholder = previousHasEmptyPlaceholder
83+
}
84+
7885
switch self {
7986
case .floating(let floating):
8087
switch floating {
8188
case .float: result += "float"
8289
case .double: result += "double"
8390
}
8491

92+
spaceBeforePlaceHolder(
93+
hasEmptyPlaceholder: hasEmptyPlaceholder,
94+
result: &result
95+
)
96+
8597
case .function(resultType: let resultType, parameters: _, variadic: _):
86-
resultType.printBefore(result: &result)
98+
let previousHasEmptyPlaceholder = hasEmptyPlaceholder
99+
hasEmptyPlaceholder = false
100+
defer {
101+
hasEmptyPlaceholder = previousHasEmptyPlaceholder
102+
}
103+
resultType.printBefore(
104+
hasEmptyPlaceholder: &hasEmptyPlaceholder,
105+
result: &result
106+
)
87107

88-
// FIXME: Clang inserts a parentheses in here if there's a non-empty
89-
// placeholder, which is Very Stateful. How should I model that?
108+
if !previousHasEmptyPlaceholder {
109+
result += "("
110+
}
90111

91112
case .integral(let integral):
92113
switch integral {
@@ -97,21 +118,47 @@ extension CType: CustomStringConvertible {
97118
case .size_t: result += "size_t"
98119
}
99120

121+
spaceBeforePlaceHolder(
122+
hasEmptyPlaceholder: hasEmptyPlaceholder,
123+
result: &result
124+
)
125+
100126
case .pointer(let pointee):
101-
pointee.printBefore(result: &result)
127+
var innerHasEmptyPlaceholder = false
128+
pointee.printBefore(
129+
hasEmptyPlaceholder: &innerHasEmptyPlaceholder,
130+
result: &result
131+
)
102132
result += "*"
103133

104134
case .qualified(const: let isConst, volatile: let isVolatile, type: let underlying):
105-
underlying.printBefore(result: &result)
135+
if isConst || isVolatile {
136+
hasEmptyPlaceholder = false
137+
}
138+
139+
underlying.printBefore(hasEmptyPlaceholder: &hasEmptyPlaceholder, result: &result)
106140

107141
// FIXME: "east const" is easier to print correctly, so do that. We could
108142
// follow Clang and decide when it's correct to print "west const" by
109143
// splitting the qualifiers before we get here.
110144
if isConst {
111-
result += " const"
145+
result += "const"
146+
hasEmptyPlaceholder = false
147+
148+
spaceBeforePlaceHolder(
149+
hasEmptyPlaceholder: hasEmptyPlaceholder,
150+
result: &result
151+
)
152+
112153
}
113154
if isVolatile {
114-
result += " volatile"
155+
result += "volatile"
156+
hasEmptyPlaceholder = false
157+
158+
spaceBeforePlaceHolder(
159+
hasEmptyPlaceholder: hasEmptyPlaceholder,
160+
result: &result
161+
)
115162
}
116163

117164
case .tag(let tag):
@@ -121,7 +168,18 @@ extension CType: CustomStringConvertible {
121168
case .union(let cUnion): result += "union \(cUnion.name)"
122169
}
123170

124-
case .void: result += "void"
171+
spaceBeforePlaceHolder(
172+
hasEmptyPlaceholder: hasEmptyPlaceholder,
173+
result: &result
174+
)
175+
176+
case .void:
177+
result += "void"
178+
179+
spaceBeforePlaceHolder(
180+
hasEmptyPlaceholder: hasEmptyPlaceholder,
181+
result: &result
182+
)
125183
}
126184
}
127185

@@ -146,13 +204,14 @@ extension CType: CustomStringConvertible {
146204

147205
/// Print the part of the type that comes after the declarator, appending
148206
/// it to the provided `result` string.
149-
func printAfter(result: inout String) {
207+
func printAfter(hasEmptyPlaceholder: inout Bool, result: inout String) {
150208
switch self {
151209
case .floating, .integral, .tag, .void: break
152210

153211
case .function(resultType: let resultType, parameters: let parameters, variadic: let variadic):
154-
// FIXME: Clang inserts a parentheses in here if there's a non-empty
155-
// placeholder, which is Very Stateful. How should I model that?
212+
if !hasEmptyPlaceholder {
213+
result += ")"
214+
}
156215

157216
result += "("
158217

@@ -167,33 +226,53 @@ extension CType: CustomStringConvertible {
167226

168227
result += ")"
169228

170-
resultType.printAfter(result: &result)
229+
var innerHasEmptyPlaceholder = false
230+
resultType.printAfter(
231+
hasEmptyPlaceholder: &innerHasEmptyPlaceholder,
232+
result: &result
233+
)
171234

172235
case .pointer(let pointee):
173-
pointee.printAfter(result: &result)
236+
var innerHasEmptyPlaceholder = false
237+
pointee.printAfter(
238+
hasEmptyPlaceholder: &innerHasEmptyPlaceholder,
239+
result: &result
240+
)
174241

175242
case .qualified(const: _, volatile: _, type: let underlying):
176-
underlying.printAfter(result: &result)
243+
underlying.printAfter(
244+
hasEmptyPlaceholder: &hasEmptyPlaceholder,
245+
result: &result
246+
)
177247
}
178248
}
179249

180250
/// Print this type into a string, with the given placeholder as the name
181251
/// of the entity being declared.
182252
public func print(placeholder: String?) -> String {
253+
var hasEmptyPlaceholder = (placeholder == nil)
183254
var result = ""
184-
printBefore(result: &result)
255+
printBefore(hasEmptyPlaceholder: &hasEmptyPlaceholder, result: &result)
185256
if let placeholder {
186-
result += " "
187257
result += placeholder
188258
}
189-
printAfter(result: &result)
259+
printAfter(hasEmptyPlaceholder: &hasEmptyPlaceholder, result: &result)
190260
return result
191261
}
192262

193263
/// Render the C type into a string that represents the type in C.
194264
public var description: String {
195265
print(placeholder: nil)
196266
}
267+
268+
private func spaceBeforePlaceHolder(
269+
hasEmptyPlaceholder: Bool,
270+
result: inout String
271+
) {
272+
if !hasEmptyPlaceholder {
273+
result += " "
274+
}
275+
}
197276
}
198277

199278
extension CType {

Tests/JExtractSwiftTests/CTypeTests.swift

+28-6
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import Testing
1717

1818
@Suite("C type system tests")
1919
struct CTypeTests {
20-
@Test("Function declaration printing")
20+
@Test("C function declaration printing")
2121
func testFunctionDeclarationPrint() {
2222
let malloc = CFunction(
2323
resultType: .pointer(.void),
@@ -27,7 +27,7 @@ struct CTypeTests {
2727
],
2828
isVariadic: false
2929
)
30-
#expect(malloc.description == "void* malloc(size_t size)")
30+
#expect(malloc.description == "void *malloc(size_t size)")
3131

3232
let free = CFunction(
3333
resultType: .void,
@@ -37,7 +37,7 @@ struct CTypeTests {
3737
],
3838
isVariadic: false
3939
)
40-
#expect(free.description == "void free(void* ptr)")
40+
#expect(free.description == "void free(void *ptr)")
4141

4242
let snprintf = CFunction(
4343
resultType: .integral(.signed(bits: 32)),
@@ -58,8 +58,8 @@ struct CTypeTests {
5858
],
5959
isVariadic: true
6060
)
61-
#expect(snprintf.description == "int32_t snprintf(int8_t* str, size_t size, int8_t const* format, ...)")
62-
#expect(snprintf.functionType.description == "int32_t(int8_t*, size_t, int8_t const*, ...)")
61+
#expect(snprintf.description == "int32_t snprintf(int8_t *str, size_t size, int8_t const *format, ...)")
62+
#expect(snprintf.functionType.description == "int32_t (int8_t *, size_t, int8_t const *, ...)")
6363

6464
let rand = CFunction(
6565
resultType: .integral(.signed(bits: 32)),
@@ -68,6 +68,28 @@ struct CTypeTests {
6868
isVariadic: false
6969
)
7070
#expect(rand.description == "int32_t rand(void)")
71-
#expect(rand.functionType.description == "int32_t(void)")
71+
#expect(rand.functionType.description == "int32_t (void)")
72+
}
73+
74+
@Test("C pointer declarator printing")
75+
func testPointerDeclaratorPrinting() {
76+
let doit = CFunction(
77+
resultType: .void,
78+
name: "doit",
79+
parameters: [
80+
.init(
81+
name: "body",
82+
type: .pointer(
83+
.function(
84+
resultType: .void,
85+
parameters: [.integral(.bool)],
86+
variadic: false
87+
)
88+
)
89+
)
90+
],
91+
isVariadic: false
92+
)
93+
#expect(doit.description == "void doit(void (*body)(_Bool))")
7294
}
7395
}

0 commit comments

Comments
 (0)