1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
|
import Std.Data.TreeSet
import Scanner.Protocol
/-!
# Generating Code from Protocol Descriptions
This turns a `Client.ProtocolDesc` into the various Lean source files containing bindings for said protocol.
-/
namespace WaylandScanner
open Client
-- TODO: Maybe use Lean's `Syntax` API here instead of a custom formatter?
private structure GeneratorState where
imports : Std.TreeSet String := .empty
code : Array String := #[]
private structure GeneratorContext where
rootModule : String
currentModule : String
private abbrev Generator := StateT GeneratorState (ReaderM GeneratorContext)
private def emitImport (mod : String) : Generator Unit := do
let current := (← read).currentModule
if current != mod then
let oldState ← get
set { oldState with imports := oldState.imports.insert mod }
return ()
private def emitLocalImport (protocolName : Identifier) (suffix : String := "") : Generator Unit := do
let root := (← read).rootModule
emitImport s!"{root}.{protocolName.pascalCase}{suffix}"
private def emitLine (line : String) : Generator Unit := do
let oldState ← get
set { oldState with code := oldState.code ++ #[line] }
private def emitNamespace {α : Type} (space : String) (gen : Generator α) : Generator α := do
emitLine s!"namespace {space}"
emitLine s!""
let res ← gen
emitLine s!""
emitLine s!"end {space}"
return res
private def Generator.run (gen : Generator Unit) : ReaderM GeneratorContext String := do
let (_, res) ← StateT.run gen {}
let imports := res.imports
|>.toArray
|>.map fun m ↦ s!"import {m}"
let lines := imports ++ #[""] ++ res.code
return String.intercalate "\n" lines.toList
private def Generator.runProtocol (rootModule : String) (protocol : ProtocolDesc) (suffix : String := "") (gen : Generator Unit) : String :=
gen.run.run { rootModule, currentModule := s!"{rootModule}.{protocol.name.pascalCase}{suffix}" }
private def Generator.runIndex (rootModule : String) (gen : Generator Unit) : String :=
gen.run.run { rootModule, currentModule := rootModule }
private def resolveInterface (iface : InterfaceRef) (suffix : String := ".Header") : Generator String := do
emitLocalImport iface.protocol suffix
return iface.interface.pascalCase
private def resolveEnum (enum : EnumRef) : Generator String := do
let iface ← resolveInterface enum.toInterfaceRef
return s!"{iface}.{enum.enum.pascalCase}"
private def typeToLean : ArgumentType → Generator String
| .int .none => do return "Int32"
| .int (.some enum) => resolveEnum enum
| .uint .none => do return "UInt32"
| .uint (.some enum) => resolveEnum enum
| .fixed => do return "Float32"
| .string .nullable => do return "Option String"
| .string .notNull => do return "String"
| .array => do return "ByteArray"
| .fd => do return "Fd"
| .object .nullable .none => do return "Option AnyObject"
| .object .nullable (.some iface) => do return s!"Option {← resolveInterface iface}"
| .object .notNull .none => do return s!"AnyObject"
| .object .notNull (.some iface) => resolveInterface iface
| .newID .none => do return s!"AnyObject"
| .newID (.some iface) => resolveInterface iface
private def marshalType : ArgumentType → String
| .int _ => ".int"
| .uint _ => ".uint"
| .fixed => ".fixed"
| .string _ => ".string"
| .object _ _ => ".object"
| .newID _ => ".object"
| .array => ".array"
| .fd => ".fd"
section Header
private def emitNameDefinitions (protocol : ProtocolDesc) : Generator Unit := do
for iface in protocol.interfaces do
emitLine s!"abbrev {iface.name.pascalCase}.name : InterfaceName := {iface.name.quoted}"
private def emitObjectDefinitions (protocol : ProtocolDesc) : Generator Unit := do
for iface in protocol.interfaces do
emitLine s!"abbrev {iface.name.pascalCase} : Type := Object {iface.name.pascalCase}.name"
private def emitEnumDefinitions (protocol : ProtocolDesc) : Generator Unit := do
for iface in protocol.interfaces do
for enum in iface.enums do
emitLine s!""
if enum.bitfield then
emitLine s!"structure {iface.name.pascalCase}.{enum.name.pascalCase} where"
for entry in enum.entries do
emitLine s!" {entry.name.camelCase} : Bool := false"
else
emitLine s!"inductive {iface.name.pascalCase}.{enum.name.pascalCase} where"
for entry in enum.entries do
emitLine s!" | {entry.name.camelCase}"
private def emitEnumMarshallable (protocol : ProtocolDesc) : Generator Unit := do
for iface in protocol.interfaces do
for enum in iface.enums do
emitLine s!""
emitNamespace s!"{iface.name.pascalCase}.{enum.name.pascalCase}" do
emitLine s!"instance : Marshallable {iface.name.pascalCase}.{enum.name.pascalCase} where"
if enum.bitfield then
emitLine s!" marshal"
emitLine s!" | .uint, μ => ArgumentMarshaller.marshalUInt <|"
emitLine s!" 0"
for entry in enum.entries do
emitLine s!" ||| (if μ.{entry.name.camelCase} then {entry.value} else 0)"
emitLine s!" | _, _ => throw \"Marshalling unexpected type, expected uint.\""
emitLine s!" demarshal"
emitLine s!" | .uint => do"
emitLine s!" let μ ← ArgumentDemarshaller.demarshalUInt"
emitLine " return {"
for entry in enum.entries do
emitLine s!" {entry.name.camelCase} := (μ &&& {entry.value}) == {entry.value}"
emitLine " }"
emitLine s!" | _ => throw \"Demarshalling unexpected type, expected uint.\""
else
emitLine s!" marshal"
for entry in enum.entries do
emitLine s!" | .int, .{entry.name.camelCase} => ArgumentMarshaller.marshalInt {entry.value}"
emitLine s!" | .uint, .{entry.name.camelCase} => ArgumentMarshaller.marshalUInt {entry.value}"
emitLine s!" | _, _ => throw \"Marshalling unexpected type, expected int or uint.\""
emitLine s!" demarshal"
let emitMatch (demarshal : String) : Generator Unit := do
emitLine s!" match (← ArgumentDemarshaller.{demarshal}) with"
for entry in enum.entries do
emitLine s!" | {entry.value} => pure .{entry.name.camelCase}"
emitLine s!" | _ => throw \"Invalid enum value.\""
emitLine s!" | .int => do"
emitMatch "demarshalInt"
emitLine s!" | .uint => do"
emitMatch "demarshalUInt"
emitLine s!" | _ => throw \"Demarshalling unexpected type, expected int or uint.\""
def generateHeader (rootModule : String) (protocol : ProtocolDesc) : String :=
Generator.runProtocol rootModule protocol ".Header" do
emitNamespace rootModule do
emitImport "Wayland.Display"
emitNameDefinitions protocol
emitLine ""
emitObjectDefinitions protocol
emitEnumDefinitions protocol
emitEnumMarshallable protocol
end Header
section Interface
private def emitEventDefinitions (protocol : ProtocolDesc) : Generator Unit := do
for iface in protocol.interfaces do
emitLine s!"inductive {iface.name.pascalCase}.Event where"
for event in iface.events do
emitLine s!" | {event.name.camelCase}"
for arg in event.arguments do
emitLine s!" ({arg.name.camelCase} : {← typeToLean arg.type})"
emitLine s!""
private def emitInterfaceInstances (protocol : ProtocolDesc) : Generator Unit := do
let emitMessageDesc (msg : MessageDesc) : Generator Unit := do
emitLine " {"
emitLine s!" name := {msg.name.quoted}"
let nullability : Nullability → String
| .nullable => ".nullable"
| .notNull => ".notNull"
let interfaceName : Option InterfaceRef → String
| .some iface => s!"(.some {iface.interface.quoted})"
| .none => ".none"
let argumentDesc (arg : ArgumentDesc) : String :=
match arg.type with
| .int _ => ".int"
| .uint _ => ".uint"
| .fixed => ".fixed"
| .string null => s!".string {nullability null}"
| .object null iface => s!".object {nullability null} {interfaceName iface}"
| .newID (.some iface) => s!".newID (.some {iface.interface.quoted})"
| .newID .none => ".string .notNull, .uint, .newID .none"
| .array => ".array"
| .fd => ".fd"
let args := msg.arguments
|>.map argumentDesc
|>.toList
|> String.intercalate ", "
emitLine s!" args := #[{args}]"
emitLine s!" since := {msg.since}"
emitLine s!" isDestructor := {msg.isDestructor}"
emitLine " },"
for iface in protocol.interfaces do
emitLine s!""
emitLine s!"instance instInterface{iface.name.pascalCase} : Interface {iface.name.pascalCase}.name where"
emitLine s!" event := {iface.name.pascalCase}.Event"
emitLine s!" version := {iface.version}"
emitLine s!" requests := #["
for req in iface.requests do
emitMessageDesc req.toMessageDesc
emitLine s!" ]"
emitLine s!" events := #["
for ev in iface.events do
emitMessageDesc ev.toMessageDesc
emitLine s!" ]"
emitLine s!" demarshalEvent"
for ev in iface.events do
emitLine s!" | {ev.opcode} => do"
emitLine s!" return .{ev.name.camelCase}"
for arg in ev.arguments do
emitLine s!" (← Marshallable.demarshal {marshalType arg.type})"
emitLine " | _ => throw s!\"Received invalid opcode\""
def generateInterface (rootModule : String) (protocol : ProtocolDesc) : String :=
Generator.runProtocol rootModule protocol ".Interface" do
emitNamespace rootModule do
emitImport "Wayland.Display"
emitLocalImport protocol.name ".Header"
emitEventDefinitions protocol
emitInterfaceInstances protocol
end Interface
section Resolved
private def emitResolvedInterfaces (protocol : ProtocolDesc) : Generator Unit := do
for iface in protocol.interfaces do
emitLine s!"instance instResolvedInterface{iface.name.pascalCase} : ResolvedInterface {iface.name.pascalCase}.name where"
emitLine s!" referencedInterfaces := #["
for ref in iface.referencedInterfaces do
let name ← resolveInterface ref (suffix := ".Interface")
emitLine s!" ⟨{name}.name, inferInstance⟩,"
emitLine s!" ]"
emitLine s!""
def generateResolved (rootModule : String) (protocol : ProtocolDesc) : String :=
Generator.runProtocol rootModule protocol ".Resolved" do
emitNamespace rootModule do
emitImport "Wayland.Display"
emitLocalImport protocol.name ".Interface"
emitResolvedInterfaces protocol
end Resolved
section Requests
private def emitRequests (protocol : ProtocolDesc) : Generator Unit := do
for iface in protocol.interfaces do
for req in iface.requests do
let baseName := s!"{iface.name.pascalCase}.{req.name.camelCase}"
let rawName :=
if req.returnType.isWlCallback
then baseName ++ ".raw"
else baseName
emitLine s!""
emitLine s!"def {rawName}"
emitLine s!" (ω : {iface.name.pascalCase})"
for arg in req.inputArguments do
emitLine s!" ({arg.name.camelCase} : {← typeToLean arg.type})"
match req.returnType with
| .unit => emitLine s!" : DisplayM σ Unit :="
| .object .none => emitLine s!" : DisplayM σ (Object ι) :="
| .object (.some iface) => emitLine s!" : DisplayM σ {← resolveInterface iface (suffix := ".Resolved")} :="
match req.returnType with
| .unit => emitLine s!" ω.send do"
| .object _ => emitLine s!" ω.sendConstructor do"
for arg in req.arguments do
match arg.type with
| .newID (.some _) =>
emitLine s!" ArgumentMarshaller.marshalAnyObject .none"
| .newID .none =>
emitLine s!" ArgumentMarshaller.marshalString (.some ι)"
emitLine s!" ArgumentMarshaller.marshalUInt (inferInstance : Interface ι).version"
emitLine s!" ArgumentMarshaller.marshalAnyObject .none"
| _ =>
emitLine s!" Marshallable.marshal {marshalType arg.type} {arg.name.camelCase}"
emitLine s!" return {req.opcode}"
-- Special case wl_callback handling
if req.returnType.isWlCallback then
emitLine s!""
emitLine s!"def {baseName}"
emitLine s!" (ω : {iface.name.pascalCase})"
for arg in req.inputArguments do
emitLine s!" ({arg.name.camelCase} : {← typeToLean arg.type})"
emitLine s!" (γ : DisplayM σ Unit)"
emitLine s!" : DisplayM σ Unit := do"
let argNames := #["ω"] ++ (req.inputArguments.map fun arg ↦ arg.name.camelCase)
|>.toList
|> String.intercalate " "
emitLine s!" let ρ ← {rawName} {argNames}"
emitLine s!" ρ.listen fun"
emitLine s!" | .done _ => do ρ.keep; γ"
def generateRequests (rootModule : String) (protocol : ProtocolDesc) : String :=
Generator.runProtocol rootModule protocol ".Requests" do
emitNamespace rootModule do
emitImport "Wayland.Display"
emitLocalImport protocol.name ".Resolved"
emitLine "variable {σ : Type} {ι : InterfaceName} [iface : ResolvedInterface ι]"
emitRequests protocol
end Requests
section Umbrella
def generateUmbrella (rootModule : String) (protocol : ProtocolDesc) : String :=
Generator.runProtocol rootModule protocol "" do
emitLocalImport protocol.name ".Header"
emitLocalImport protocol.name ".Interface"
emitLocalImport protocol.name ".Resolved"
emitLocalImport protocol.name ".Requests"
end Umbrella
section Index
def generateIndex (rootModule : String) (protocols : Array ProtocolDesc) : String :=
Generator.runIndex rootModule do
for protocol in protocols do
emitLocalImport protocol.name
end Index
end WaylandScanner
|