-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy pathClosurePattern.swift
More file actions
139 lines (119 loc) · 4.06 KB
/
ClosurePattern.swift
File metadata and controls
139 lines (119 loc) · 4.06 KB
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
struct ClosurePattern: SwiftRepresentable, Equatable, Hashable, Comparable {
static func < (lhs: ClosurePattern, rhs: ClosurePattern) -> Bool {
lhs.closureType.source < rhs.closureType.source
}
let nullable: Bool
let void: Bool
let argCount: Int
var name: SwiftSource {
"ClosureAttribute\(String(argCount))\(nullable ? "Optional" : "")\(void ? "Void" : "")"
}
var indexes: [String] { (0 ..< argCount).map(String.init) }
private var typeNames: [SwiftSource] {
indexes.map { "A\($0)" } + (void ? [] : ["ReturnType"])
}
private var closureType: SwiftSource {
let closure: SwiftSource = "(\(sequence: Array(typeNames.prefix(argCount)))) -> \(void ? "Void" : "ReturnType")"
return nullable ? "(\(closure))?" : closure
}
func getter(name: SwiftSource) -> SwiftSource {
let getFunction: SwiftSource
if nullable {
getFunction = """
guard let function = jsObject[\(name)].function else {
return nil
}
"""
} else {
getFunction = "let function = jsObject[\(name)].function!"
}
let call: SwiftSource = "function(\(sequence: indexes.map { "_toJSValue($\($0))" }))"
let closureBody: SwiftSource
if void {
closureBody = call
} else {
// XXX: For nullable return types, should not use `!` here
closureBody = "\(call).fromJSValue()!"
}
return """
\(getFunction)
return { \(closureBody) }
"""
}
private func jsClosureWrapper(name: SwiftSource) -> SwiftSource {
let call: SwiftSource = "\(name)(\(sequence: indexes.map { "$0[\($0)].fromJSValue()!" }))"
let body: SwiftSource
if void {
body = """
\(call)
return .undefined
"""
} else {
body = "_toJSValue(\(call))"
}
return """
JSClosure { \(argCount == 0 ? "_ in" : "")
\(body)
}.jsValue
"""
}
func setter(name: SwiftSource) -> SwiftSource {
let setClosure: SwiftSource = "jsObject[\(name)] = \(jsClosureWrapper(name: "newValue"))"
if nullable {
return """
if let newValue = newValue {
\(setClosure)
} else {
jsObject[\(name)] = .null
}
"""
} else {
return setClosure
}
}
var typeParams: SwiftSource {
if typeNames.isEmpty { return "" }
return "<\(sequence: typeNames)>"
}
var whereClause: SwiftSource {
if typeNames.isEmpty { return "" }
return "\nwhere \(sequence: typeNames.map { "\($0): JSValueCompatible" })"
}
var propertyWrapper: SwiftSource {
"""
@propertyWrapper public final class \(name)\(typeParams) \(whereClause) {
@usableFromInline let jsObject: JSObject
@usableFromInline let name: JSString
@inlinable public init(jsObject: JSObject, name: JSString) {
self.jsObject = jsObject
self.name = name
}
@inlinable public var wrappedValue: \(closureType) {
get { \(name)[name, in: jsObject] }
set { \(name)[name, in: jsObject] = newValue }
}
@inlinable public static subscript(name: JSString, in jsObject: JSObject) -> \(closureType) {
get {
\(getter)
}
set {
\(setter)
}
}
}
"""
}
var toJSValue: SwiftSource {
let escaping: SwiftSource = nullable ? "" : "@escaping"
return """
@inlinable public func _toJSValue\(typeParams)(_ value: \(escaping) \(closureType)) -> JSValue \(whereClause) {
\(jsClosureWrapper(name: nullable ? "value?" : "value"))
}
"""
}
var swiftRepresentation: SwiftSource {
"""
\(toJSValue)
"""
}
}