@tutao/licc 3.98.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +137 -0
- package/cli.js +0 -0
- package/dist/Accumulator.js +23 -0
- package/dist/KotlinGenerator.js +224 -0
- package/dist/Parser.js +49 -0
- package/dist/SwiftGenerator.js +232 -0
- package/dist/TypescriptGenerator.js +199 -0
- package/dist/cli.js +83 -0
- package/dist/common.js +18 -0
- package/dist/index.js +118 -0
- package/dist/tsbuildinfo +1 -0
- package/lib/Accumulator.ts +30 -0
- package/lib/KotlinGenerator.ts +241 -0
- package/lib/Parser.ts +68 -0
- package/lib/SwiftGenerator.ts +248 -0
- package/lib/TypescriptGenerator.ts +222 -0
- package/lib/cli.ts +94 -0
- package/lib/common.ts +91 -0
- package/lib/index.ts +126 -0
- package/package.json +25 -0
- package/test/ParserTest.ts +124 -0
- package/test/Suite.ts +6 -0
- package/test/tsconfig.json +19 -0
- package/tsconfig.json +111 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import {FacadeDefinition, getArgs, LangGenerator, MethodDefinition, minusculize, RenderedType, StructDefinition, TypeRefDefinition} from "./common.js"
|
|
2
|
+
import {Accumulator} from "./Accumulator.js"
|
|
3
|
+
import {ParsedType, parseType} from "./Parser.js"
|
|
4
|
+
|
|
5
|
+
export class KotlinGenerator implements LangGenerator {
|
|
6
|
+
|
|
7
|
+
generateGlobalDispatcher(name: string, facadeNames: string[]): string {
|
|
8
|
+
const acc = new Accumulator()
|
|
9
|
+
KotlinGenerator.generateImports(acc)
|
|
10
|
+
acc.line(`import de.tutao.tutanota.ipc.*`)
|
|
11
|
+
acc.line()
|
|
12
|
+
acc.line(`class ${name} (`)
|
|
13
|
+
const methodAcc = acc.indent()
|
|
14
|
+
methodAcc.line(`json: Json,`)
|
|
15
|
+
for (let facadeName of facadeNames) {
|
|
16
|
+
methodAcc.line(`${minusculize(facadeName)} : ${facadeName},`)
|
|
17
|
+
}
|
|
18
|
+
acc.line(") {")
|
|
19
|
+
for (let facadeName of facadeNames) {
|
|
20
|
+
methodAcc.line(`private val ${minusculize(facadeName)}: ${facadeName}ReceiveDispatcher = ${facadeName}ReceiveDispatcher(json, ${minusculize(facadeName)})`)
|
|
21
|
+
}
|
|
22
|
+
methodAcc.line()
|
|
23
|
+
|
|
24
|
+
methodAcc.line(`suspend fun dispatch(facadeName: String, methodName: String, args: List<String>): String {`)
|
|
25
|
+
const whenAcc = methodAcc.indent()
|
|
26
|
+
whenAcc.line(`return when (facadeName) {`)
|
|
27
|
+
const caseAcc = whenAcc.indent()
|
|
28
|
+
for (let facadeName of facadeNames) {
|
|
29
|
+
caseAcc.line(`"${facadeName}" -> this.${minusculize(facadeName)}.dispatch(methodName, args)`)
|
|
30
|
+
}
|
|
31
|
+
caseAcc.line(`else -> throw Error("unknown facade: $facadeName")`)
|
|
32
|
+
whenAcc.line(`}`)
|
|
33
|
+
methodAcc.line(`}`)
|
|
34
|
+
acc.line(`}`)
|
|
35
|
+
return acc.finish()
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
handleStructDefinition(definition: StructDefinition): string {
|
|
39
|
+
const acc = new Accumulator()
|
|
40
|
+
KotlinGenerator.generateImports(acc)
|
|
41
|
+
acc.line()
|
|
42
|
+
if (definition.doc) {
|
|
43
|
+
this.generateDocComment(acc, definition.doc)
|
|
44
|
+
}
|
|
45
|
+
acc.line("@Serializable")
|
|
46
|
+
acc.line(`data class ${definition.name}(`)
|
|
47
|
+
const fieldGenerator = acc.indent()
|
|
48
|
+
for (const [name, fieldDefinition] of Object.entries(definition.fields)) {
|
|
49
|
+
const renderedType = typeNameKotlin(fieldDefinition)
|
|
50
|
+
fieldGenerator.line(`val ${name}: ${renderedType.name},`)
|
|
51
|
+
}
|
|
52
|
+
acc.line(")")
|
|
53
|
+
return acc.finish()
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
private static generateImports(acc: Accumulator) {
|
|
57
|
+
acc.line("package de.tutao.tutanota.ipc")
|
|
58
|
+
acc.line()
|
|
59
|
+
acc.line("import kotlinx.serialization.*")
|
|
60
|
+
acc.line("import kotlinx.serialization.json.*")
|
|
61
|
+
acc.line()
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
private generateDocComment(acc: Accumulator, comment: string | null | undefined) {
|
|
65
|
+
if (!comment) return
|
|
66
|
+
acc.line("/**")
|
|
67
|
+
acc.line(` * ${comment}`)
|
|
68
|
+
acc.line(" */")
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
generateFacade(definition: FacadeDefinition): string {
|
|
72
|
+
const acc = new Accumulator()
|
|
73
|
+
KotlinGenerator.generateImports(acc)
|
|
74
|
+
this.generateDocComment(acc, definition.doc)
|
|
75
|
+
acc.line(`interface ${definition.name} {`)
|
|
76
|
+
const methodAcc = acc.indent()
|
|
77
|
+
for (const [name, methodDefinition] of Object.entries(definition.methods)) {
|
|
78
|
+
this.generateDocComment(methodAcc, methodDefinition.doc)
|
|
79
|
+
KotlinGenerator.generateMethodSignature(methodAcc, name, methodDefinition)
|
|
80
|
+
}
|
|
81
|
+
acc.line("}")
|
|
82
|
+
return acc.finish()
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
generateReceiveDispatcher(definition: FacadeDefinition): string {
|
|
86
|
+
const acc = new Accumulator()
|
|
87
|
+
// Some names might clash, we don't read this file, we don't care
|
|
88
|
+
acc.line(`@file:Suppress("NAME_SHADOWING")`)
|
|
89
|
+
KotlinGenerator.generateImports(acc)
|
|
90
|
+
acc.line(`class ${definition.name}ReceiveDispatcher(`)
|
|
91
|
+
acc.indent().line(`private val json: Json,`)
|
|
92
|
+
acc.indent().line(`private val facade: ${definition.name},`)
|
|
93
|
+
acc.line(`) {`)
|
|
94
|
+
const methAcc = acc.indent()
|
|
95
|
+
methAcc.line()
|
|
96
|
+
methAcc.line(`suspend fun dispatch(method: String, arg: List<String>): String {`)
|
|
97
|
+
const whenAcc = methAcc.indent()
|
|
98
|
+
whenAcc.line(`when (method) {`)
|
|
99
|
+
const caseAcc = whenAcc.indent()
|
|
100
|
+
for (const [methodName, methodDef] of Object.entries(definition.methods)) {
|
|
101
|
+
caseAcc.line(`"${methodName}" -> {`)
|
|
102
|
+
const arg = getArgs(methodName, methodDef)
|
|
103
|
+
const decodedArgs = []
|
|
104
|
+
for (let i = 0; i < arg.length; i++) {
|
|
105
|
+
const {name: argName, type} = arg[i]
|
|
106
|
+
const renderedArgType = typeNameKotlin(type)
|
|
107
|
+
decodedArgs.push([argName, renderedArgType] as const)
|
|
108
|
+
}
|
|
109
|
+
const varAcc = caseAcc.indent()
|
|
110
|
+
for (let i = 0; i < arg.length; i++) {
|
|
111
|
+
const [argName, renderedType] = decodedArgs[i]
|
|
112
|
+
varAcc.line(`val ${argName}: ${renderedType.name} = json.decodeFromString(arg[${i}])`)
|
|
113
|
+
}
|
|
114
|
+
varAcc.line(`val result: ${typeNameKotlin(methodDef.ret).name} = this.facade.${methodName}(`)
|
|
115
|
+
for (let i = 0; i < arg.length; i++) {
|
|
116
|
+
const [argName] = decodedArgs[i]
|
|
117
|
+
varAcc.indent().line(`${argName},`)
|
|
118
|
+
}
|
|
119
|
+
varAcc.line(`)`)
|
|
120
|
+
varAcc.line(`return json.encodeToString(result)`)
|
|
121
|
+
caseAcc.line(`}`)
|
|
122
|
+
}
|
|
123
|
+
caseAcc.line(`else -> throw Error("unknown method for ${definition.name}: $method")`)
|
|
124
|
+
whenAcc.line(`}`)
|
|
125
|
+
methAcc.line(`}`)
|
|
126
|
+
acc.line(`}`)
|
|
127
|
+
return acc.finish()
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
private static generateNativeInterface(): string {
|
|
131
|
+
const acc = new Accumulator()
|
|
132
|
+
KotlinGenerator.generateImports(acc)
|
|
133
|
+
acc.line(`interface NativeInterface {`)
|
|
134
|
+
acc.indent().line(`suspend fun sendRequest(requestType: String, args: List<String>): String`)
|
|
135
|
+
acc.line(`}`)
|
|
136
|
+
return acc.finish()
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
private static generateMethodSignature(methodGenerator: Accumulator, name: string, methodDefinition: MethodDefinition, prefix: string = "") {
|
|
140
|
+
methodGenerator.line(`${prefix} suspend fun ${name}(`)
|
|
141
|
+
const argGenerator = methodGenerator.indent()
|
|
142
|
+
for (const argument of getArgs(name, methodDefinition)) {
|
|
143
|
+
const renderedArgument = typeNameKotlin(argument.type)
|
|
144
|
+
argGenerator.line(`${argument.name}: ${renderedArgument.name},`)
|
|
145
|
+
}
|
|
146
|
+
const renderedReturn = typeNameKotlin(methodDefinition.ret)
|
|
147
|
+
methodGenerator.line(`): ${renderedReturn.name}`)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
generateSendDispatcher(definition: FacadeDefinition): string {
|
|
151
|
+
const acc = new Accumulator()
|
|
152
|
+
KotlinGenerator.generateImports(acc)
|
|
153
|
+
const classBodyAcc = acc.indent()
|
|
154
|
+
acc.line(`class ${definition.name}SendDispatcher (`)
|
|
155
|
+
classBodyAcc.line(`private val json: Json,`)
|
|
156
|
+
classBodyAcc.line(`private val transport : NativeInterface,`)
|
|
157
|
+
acc.line(`) : ${definition.name} {`)
|
|
158
|
+
classBodyAcc.line(`private val encodedFacade = json.encodeToString("${definition.name}")`)
|
|
159
|
+
for (const [methodName, methodDefinition] of Object.entries(definition.methods)) {
|
|
160
|
+
KotlinGenerator.generateMethodSignature(classBodyAcc, methodName, methodDefinition, "override")
|
|
161
|
+
classBodyAcc.line("{")
|
|
162
|
+
|
|
163
|
+
const methodBodyAcc = classBodyAcc.indent()
|
|
164
|
+
methodBodyAcc.line(`val encodedMethod = json.encodeToString("${methodName}")`)
|
|
165
|
+
methodBodyAcc.line("val args : MutableList<String> = mutableListOf()")
|
|
166
|
+
for (let arg of getArgs(methodName, methodDefinition)) {
|
|
167
|
+
methodBodyAcc.line(`args.add(json.encodeToString(${arg.name}))`)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (methodDefinition.ret !== "void") {
|
|
171
|
+
methodBodyAcc.line(`val result = this.transport.sendRequest("ipc", listOf(encodedFacade, encodedMethod) + args)`)
|
|
172
|
+
methodBodyAcc.line(`return json.decodeFromString(result)`)
|
|
173
|
+
} else {
|
|
174
|
+
methodBodyAcc.line(`this.transport.sendRequest("ipc", listOf(encodedFacade, encodedMethod) + args)`)
|
|
175
|
+
}
|
|
176
|
+
classBodyAcc.line(`}`)
|
|
177
|
+
classBodyAcc.line()
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
acc.line(`}`)
|
|
182
|
+
return acc.finish()
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
generateExtraFiles(): Record<string, string> {
|
|
186
|
+
return {
|
|
187
|
+
"NativeInterface": KotlinGenerator.generateNativeInterface()
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
generateTypeRef(outDir: string, definitionPath: string, definition: TypeRefDefinition): string | null {
|
|
192
|
+
if (definition.location.kotlin) {
|
|
193
|
+
const acc = new Accumulator()
|
|
194
|
+
acc.line(`package de.tutao.tutanota.ipc`)
|
|
195
|
+
acc.line(`typealias ${definition.name} = ${definition.location.kotlin}`)
|
|
196
|
+
return acc.finish()
|
|
197
|
+
} else {
|
|
198
|
+
return null
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function typeNameKotlin(name: string): RenderedType {
|
|
204
|
+
const parsed = parseType(name)
|
|
205
|
+
return renderKotlinType(parsed)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function renderKotlinType(parsed: ParsedType): RenderedType {
|
|
209
|
+
const {baseName, nullable, external} = parsed
|
|
210
|
+
switch (baseName) {
|
|
211
|
+
case "List":
|
|
212
|
+
const renderedListInner = renderKotlinType(parsed.generics[0])
|
|
213
|
+
return {
|
|
214
|
+
externals: renderedListInner.externals,
|
|
215
|
+
name: maybeNullable(`List<${renderedListInner.name}>`, nullable)
|
|
216
|
+
}
|
|
217
|
+
case "Map":
|
|
218
|
+
const renderedKey = renderKotlinType(parsed.generics[0])
|
|
219
|
+
const renderedValue = renderKotlinType(parsed.generics[1])
|
|
220
|
+
return {
|
|
221
|
+
externals: [...renderedKey.externals, ...renderedValue.externals],
|
|
222
|
+
name: maybeNullable(`Map<${renderedKey.name}, ${renderedValue.name}>`, nullable)
|
|
223
|
+
}
|
|
224
|
+
case "string":
|
|
225
|
+
return {externals: [], name: maybeNullable("String", nullable)}
|
|
226
|
+
case "boolean":
|
|
227
|
+
return {externals: [], name: maybeNullable("Boolean", nullable)}
|
|
228
|
+
case "number":
|
|
229
|
+
return {externals: [], name: maybeNullable("Int", nullable)}
|
|
230
|
+
case "bytes":
|
|
231
|
+
return {externals: [], name: maybeNullable("DataWrapper", nullable)}
|
|
232
|
+
case "void":
|
|
233
|
+
return {externals: [], name: maybeNullable("Unit", nullable)}
|
|
234
|
+
default:
|
|
235
|
+
return {externals: [baseName], name: maybeNullable(baseName, nullable)}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function maybeNullable(name: string, nullable: boolean) {
|
|
240
|
+
return nullable ? `${name}?` : name
|
|
241
|
+
}
|
package/lib/Parser.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
export const PRIMITIVES = ["string", "boolean", "number", "bytes", "void"]
|
|
2
|
+
const KOTLIN_KEYWORDS = [
|
|
3
|
+
"as", "break", "class", "continue", "do", "else", "false", "for", "fun", "if", "in", "interface", "is", "null", "object", "package", "return", "super",
|
|
4
|
+
"this", "throw", "true", "try", "typealias", "typeof", "val", "var", "when", "while"
|
|
5
|
+
]
|
|
6
|
+
const TYPESCRIPT_KEYWORDS = [
|
|
7
|
+
"var", "const", "let", "break", "return", "case", "catch", "class", "continue", "debugger", "default", "delete", "do", "else", "enum", "export", "extends",
|
|
8
|
+
"false", "finally", "for", "function", "if", "import", "in", "instanceOf", "new", "null", "return", "super", "switch", "this", "throw", "true", "try",
|
|
9
|
+
"typeOf", "void", "while", "with"
|
|
10
|
+
]
|
|
11
|
+
const SWIFT_KEYWORDS = [
|
|
12
|
+
"Class", "deinit", "Enum", "extension", "Func", "import", "Init", "internal", "Let", "operator", "private", "protocol", "public", "static", "struct", "subscript",
|
|
13
|
+
"typealias", "var", "break", "case", "continue", "default", "do", "else", "fallthrough", "for", "if", "in", "return", "switch", "where", "while",
|
|
14
|
+
"as", "dynamicType", "false", "is", "nil", "self", "Self", "super", "true", "_COLUMN_", "_FILE_", "_FUNCTION_", "_LINE_",
|
|
15
|
+
"associativity", "convenience", "dynamic", "didSet", "final", "get", "infix", "inout", "lazy", "left", "mutating", "none", "nonmutating",
|
|
16
|
+
"optional", "override", "postfix", "precedence", "prefix", "Protocol", "required", "right", "set", "Type", "unowned", "weak", "willSet"
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
const FORBIDDEN_IDENTIFIERS = new Set([...KOTLIN_KEYWORDS, ...TYPESCRIPT_KEYWORDS, ...SWIFT_KEYWORDS])
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* parse a type definition string from the json into a structure that contains all information needed to render
|
|
23
|
+
* language-specific type defs
|
|
24
|
+
*/
|
|
25
|
+
export function parseType(typeString: string): ParsedType {
|
|
26
|
+
let nullable = false
|
|
27
|
+
typeString = typeString.trim()
|
|
28
|
+
if (typeString.endsWith("?")) {
|
|
29
|
+
nullable = true
|
|
30
|
+
typeString = typeString.slice(0, -1)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const listMatch = typeString.match(/^\s*List<\s*(.*)\s*>\s*$/)
|
|
34
|
+
if (listMatch) {
|
|
35
|
+
const nested = parseType(listMatch[1])
|
|
36
|
+
return {baseName: "List", generics: [nested], nullable, external: false}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const mapMatch = typeString.match(/^\s*Map<\s*(.*?),\s*(.*?)\s*>\s*$/)
|
|
40
|
+
if (mapMatch) {
|
|
41
|
+
const nestedKey = parseType(mapMatch[1])
|
|
42
|
+
const nestedValue = parseType(mapMatch[2])
|
|
43
|
+
return {baseName: "Map", generics: [nestedKey, nestedValue], nullable, external: false}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// this is a basic type without generic params
|
|
47
|
+
const external = !PRIMITIVES.includes(typeString)
|
|
48
|
+
const willBreakAtLeastOneLang = FORBIDDEN_IDENTIFIERS.has(typeString) && external
|
|
49
|
+
const startsWithLetterOrUnderscore = typeString.match(/^[_a-zA-Z]/)
|
|
50
|
+
if (willBreakAtLeastOneLang || !startsWithLetterOrUnderscore) {
|
|
51
|
+
throw new Error(`illegal identifier: "${typeString}"`)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return {baseName: typeString, generics: [], external, nullable}
|
|
55
|
+
} // frontend type
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
// {basename: List, generics: [{basename: string, generics: []}]>
|
|
59
|
+
export interface ParsedType {
|
|
60
|
+
// this needs an import
|
|
61
|
+
external: boolean
|
|
62
|
+
// this argument or return can be null
|
|
63
|
+
nullable: boolean
|
|
64
|
+
// name as given in the json file, without generics
|
|
65
|
+
baseName: string
|
|
66
|
+
// contained generic types, if any
|
|
67
|
+
generics: ParsedType[]
|
|
68
|
+
}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import {FacadeDefinition, getArgs, LangGenerator, MethodDefinition, minusculize, RenderedType, StructDefinition, TypeRefDefinition} from "./common.js"
|
|
2
|
+
import {Accumulator} from "./Accumulator.js"
|
|
3
|
+
import {ParsedType, parseType} from "./Parser.js"
|
|
4
|
+
|
|
5
|
+
export class SwiftGenerator implements LangGenerator {
|
|
6
|
+
|
|
7
|
+
handleStructDefinition(definition: StructDefinition): string {
|
|
8
|
+
const acc = new Accumulator()
|
|
9
|
+
this.generateDocComment(acc, definition.doc)
|
|
10
|
+
acc.line(`public struct ${definition.name} : Codable {`)
|
|
11
|
+
const fieldGenerator = acc.indent()
|
|
12
|
+
for (const [name, fieldDefinition] of Object.entries(definition.fields)) {
|
|
13
|
+
const renderedType = typeNameSwift(fieldDefinition)
|
|
14
|
+
fieldGenerator.line(`let ${name}: ${renderedType.name}`)
|
|
15
|
+
}
|
|
16
|
+
acc.line("}")
|
|
17
|
+
return acc.finish()
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
private generateDocComment(acc: Accumulator, comment: string | null | undefined) {
|
|
21
|
+
if (!comment) return
|
|
22
|
+
acc.line("/**")
|
|
23
|
+
acc.line(` * ${comment}`)
|
|
24
|
+
acc.line(" */")
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
generateFacade(definition: FacadeDefinition): string {
|
|
28
|
+
const acc = new Accumulator()
|
|
29
|
+
acc.line("import Foundation")
|
|
30
|
+
acc.line()
|
|
31
|
+
this.generateDocComment(acc, definition.doc)
|
|
32
|
+
acc.line(`public protocol ${definition.name} {`)
|
|
33
|
+
const methodAcc = acc.indent()
|
|
34
|
+
for (const [name, methodDefinition] of Object.entries(definition.methods)) {
|
|
35
|
+
this.generateDocComment(methodAcc, methodDefinition.doc)
|
|
36
|
+
SwiftGenerator.generateMethodSignature(methodAcc, name, methodDefinition)
|
|
37
|
+
}
|
|
38
|
+
acc.line("}")
|
|
39
|
+
return acc.finish()
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
private static generateMethodSignature(methodGenerator: Accumulator, name: string, methodDefinition: MethodDefinition) {
|
|
43
|
+
methodGenerator.line(`func ${name}(`)
|
|
44
|
+
const argGenerator = methodGenerator.indent()
|
|
45
|
+
const args = getArgs(name, methodDefinition)
|
|
46
|
+
const lastArg = args[args.length - 1]
|
|
47
|
+
for (const argument of args) {
|
|
48
|
+
const renderedArgument = typeNameSwift(argument.type)
|
|
49
|
+
const argLine =
|
|
50
|
+
`_ ${argument.name}: ${renderedArgument.name}` +
|
|
51
|
+
((argument === lastArg) ? "" : ",")
|
|
52
|
+
argGenerator.line(argLine)
|
|
53
|
+
}
|
|
54
|
+
const renderedReturn = typeNameSwift(methodDefinition.ret)
|
|
55
|
+
methodGenerator.line(`) async throws -> ${renderedReturn.name}`)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
generateReceiveDispatcher(definition: FacadeDefinition): string {
|
|
59
|
+
const acc = new Accumulator()
|
|
60
|
+
acc.line("import Foundation")
|
|
61
|
+
acc.line()
|
|
62
|
+
acc.line(`public class ${definition.name}ReceiveDispatcher {`)
|
|
63
|
+
const methodAcc = acc.indent()
|
|
64
|
+
methodAcc.line(`let facade: ${definition.name}`)
|
|
65
|
+
methodAcc.line(`init(facade: ${definition.name}) {`)
|
|
66
|
+
methodAcc.indent().line(`self.facade = facade`)
|
|
67
|
+
methodAcc.line(`}`)
|
|
68
|
+
|
|
69
|
+
methodAcc.line(`func dispatch(method: String, arg: [String]) async throws -> String {`)
|
|
70
|
+
const switchAcc = methodAcc.indent()
|
|
71
|
+
switchAcc.line(`switch method {`)
|
|
72
|
+
const caseAcc = switchAcc.indent()
|
|
73
|
+
for (const [methodName, method] of Object.entries(definition.methods)) {
|
|
74
|
+
const arg = getArgs(methodName, method)
|
|
75
|
+
const decodedArgs = []
|
|
76
|
+
for (let i = 0; i < arg.length; i++) {
|
|
77
|
+
const {name: argName, type} = arg[i]
|
|
78
|
+
const renderedArgType = typeNameSwift(type)
|
|
79
|
+
decodedArgs.push([argName, renderedArgType] as const)
|
|
80
|
+
}
|
|
81
|
+
switchAcc.line(`case "${methodName}":`)
|
|
82
|
+
for (let i = 0; i < arg.length; i++) {
|
|
83
|
+
const [argName, argType] = decodedArgs[i]
|
|
84
|
+
caseAcc.line(`let ${argName} = try! JSONDecoder().decode(${argType.name}.self, from: arg[${i}].data(using: .utf8)!)`)
|
|
85
|
+
}
|
|
86
|
+
if (method.ret === "void") {
|
|
87
|
+
caseAcc.line(`try await self.facade.${methodName}(`)
|
|
88
|
+
} else {
|
|
89
|
+
caseAcc.line(`let result = try await self.facade.${methodName}(`)
|
|
90
|
+
}
|
|
91
|
+
for (let i = 0; i < arg.length; i++) {
|
|
92
|
+
const comma = i === arg.length - 1 ? "" : ","
|
|
93
|
+
caseAcc.indent().line(arg[i].name + comma)
|
|
94
|
+
}
|
|
95
|
+
caseAcc.line(")")
|
|
96
|
+
if (method.ret === "void") {
|
|
97
|
+
caseAcc.line(`return "null"`)
|
|
98
|
+
} else {
|
|
99
|
+
caseAcc.line(`return toJson(result)`)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
switchAcc.line(`default:`)
|
|
103
|
+
caseAcc.line(`fatalError("licc messed up! \\(method)")`)
|
|
104
|
+
switchAcc.line(`}`)
|
|
105
|
+
methodAcc.line(`}`)
|
|
106
|
+
|
|
107
|
+
acc.line(`}`)
|
|
108
|
+
return acc.finish()
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
generateGlobalDispatcher(name: string, facadeNames: Array<string>): string {
|
|
112
|
+
const acc = new Accumulator()
|
|
113
|
+
acc.line(`public class ${name} {`)
|
|
114
|
+
const methodAcc = acc.indent()
|
|
115
|
+
|
|
116
|
+
for (let facadeName of facadeNames) {
|
|
117
|
+
methodAcc.line(`private let ${minusculize(facadeName)}: ${facadeName}ReceiveDispatcher`)
|
|
118
|
+
}
|
|
119
|
+
methodAcc.line()
|
|
120
|
+
methodAcc.line(`init(`)
|
|
121
|
+
const lastFacadeName = facadeNames[facadeNames.length - 1]
|
|
122
|
+
for (let facadeName of facadeNames) {
|
|
123
|
+
const comma = facadeName === lastFacadeName ? "" : ","
|
|
124
|
+
methodAcc.indent().line(`${minusculize(facadeName)} : ${facadeName}${comma}`)
|
|
125
|
+
}
|
|
126
|
+
methodAcc.line(`) {`)
|
|
127
|
+
for (let facadeName of facadeNames) {
|
|
128
|
+
methodAcc.indent().line(`self.${minusculize(facadeName)} = ${facadeName}ReceiveDispatcher(facade: ${minusculize(facadeName)})`)
|
|
129
|
+
}
|
|
130
|
+
methodAcc.line(`}`)
|
|
131
|
+
methodAcc.line()
|
|
132
|
+
|
|
133
|
+
methodAcc.line(`func dispatch(facadeName: String, methodName: String, args: Array<String>) async throws -> String {`)
|
|
134
|
+
const switchAcc = methodAcc.indent()
|
|
135
|
+
switchAcc.line(`switch facadeName {`)
|
|
136
|
+
const caseAcc = switchAcc.indent()
|
|
137
|
+
for (let facadeName of facadeNames) {
|
|
138
|
+
caseAcc.line(`case "${facadeName}":`)
|
|
139
|
+
caseAcc.indent().line(`return try await self.${minusculize(facadeName)}.dispatch(method: methodName, arg: args)`)
|
|
140
|
+
}
|
|
141
|
+
caseAcc.line(`default:`)
|
|
142
|
+
caseAcc.indent().line(`fatalError("licc messed up! " + facadeName)`)
|
|
143
|
+
switchAcc.line(`}`)
|
|
144
|
+
methodAcc.line(`}`)
|
|
145
|
+
acc.line(`}`)
|
|
146
|
+
return acc.finish()
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
generateSendDispatcher(definition: FacadeDefinition): string {
|
|
150
|
+
const acc = new Accumulator()
|
|
151
|
+
acc.line("import Foundation")
|
|
152
|
+
acc.line()
|
|
153
|
+
acc.line(`class ${definition.name}SendDispatcher : ${definition.name} {`)
|
|
154
|
+
const classBodyAcc = acc.indent()
|
|
155
|
+
classBodyAcc.line(`private let transport: NativeInterface`)
|
|
156
|
+
classBodyAcc.line(`init(transport: NativeInterface) { self.transport = transport }`)
|
|
157
|
+
classBodyAcc.line()
|
|
158
|
+
for (const [methodName, methodDefinition] of Object.entries(definition.methods)) {
|
|
159
|
+
SwiftGenerator.generateMethodSignature(classBodyAcc, methodName, methodDefinition)
|
|
160
|
+
const methodBodyAcc = classBodyAcc.indent()
|
|
161
|
+
methodBodyAcc.line("{")
|
|
162
|
+
const args = getArgs(methodName, methodDefinition)
|
|
163
|
+
if (args.length > 0) {
|
|
164
|
+
methodBodyAcc.line("var args = [String]()")
|
|
165
|
+
} else {
|
|
166
|
+
// let instead of var so that it shuts up about mutability unused
|
|
167
|
+
methodBodyAcc.line("let args = [String]()")
|
|
168
|
+
}
|
|
169
|
+
for (let arg of args) {
|
|
170
|
+
methodBodyAcc.line(`args.append(toJson(${arg.name}))`)
|
|
171
|
+
}
|
|
172
|
+
methodBodyAcc.line(`let encodedFacadeName = toJson("${definition.name}")`)
|
|
173
|
+
methodBodyAcc.line(`let encodedMethodName = toJson("${methodName}")`)
|
|
174
|
+
if (methodDefinition.ret !== "void") {
|
|
175
|
+
methodBodyAcc.line(`let returnValue = try await self.transport.sendRequest(requestType: "ipc", args: [encodedFacadeName, encodedMethodName] + args)`)
|
|
176
|
+
methodBodyAcc.line(`return try! JSONDecoder().decode(${typeNameSwift(methodDefinition.ret).name}.self, from: returnValue.data(using: .utf8)!)`)
|
|
177
|
+
} else {
|
|
178
|
+
methodBodyAcc.line(`let _ = try await self.transport.sendRequest(requestType: "ipc", args: [encodedFacadeName, encodedMethodName] + args)`)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
methodBodyAcc.line(`}`)
|
|
182
|
+
classBodyAcc.line()
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
acc.line(`}`)
|
|
186
|
+
return acc.finish()
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
generateExtraFiles(): Record<string, string> {
|
|
190
|
+
return {
|
|
191
|
+
"NativeInterface": SwiftGenerator.generateNativeInterface()
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
private static generateNativeInterface(): string {
|
|
196
|
+
const acc = new Accumulator()
|
|
197
|
+
acc.line(`protocol NativeInterface {`)
|
|
198
|
+
acc.indent().line(`func sendRequest(requestType: String, args: [String]) async throws -> String`)
|
|
199
|
+
acc.line(`}`)
|
|
200
|
+
acc.line()
|
|
201
|
+
acc.line("func toJson<T>(_ thing: T) -> String where T : Encodable {")
|
|
202
|
+
acc.indent().line("return String(data: try! JSONEncoder().encode(thing), encoding: .utf8)!")
|
|
203
|
+
acc.line("}")
|
|
204
|
+
acc.line()
|
|
205
|
+
return acc.finish()
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
generateTypeRef(outDir: string, definitionPath: string, definition: TypeRefDefinition): string | null {
|
|
209
|
+
return null
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function typeNameSwift(name: string): RenderedType {
|
|
214
|
+
const parsed = parseType(name)
|
|
215
|
+
return renderSwiftType(parsed)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function renderSwiftType(parsed: ParsedType): RenderedType {
|
|
219
|
+
const {baseName, nullable} = parsed
|
|
220
|
+
switch (baseName) {
|
|
221
|
+
case "List":
|
|
222
|
+
const renderedListInner = renderSwiftType(parsed.generics[0])
|
|
223
|
+
return {externals: renderedListInner.externals, name: maybeNullable(`[${renderedListInner.name}]`, nullable)}
|
|
224
|
+
case "Map":
|
|
225
|
+
const renderedKey = renderSwiftType(parsed.generics[0])
|
|
226
|
+
const renderedValue = renderSwiftType(parsed.generics[1])
|
|
227
|
+
return {
|
|
228
|
+
externals: [...renderedKey.externals, ...renderedValue.externals],
|
|
229
|
+
name: maybeNullable(`[${renderedKey.name} : ${renderedValue.name}]`, nullable)
|
|
230
|
+
}
|
|
231
|
+
case "string":
|
|
232
|
+
return {externals: [], name: maybeNullable("String", nullable)}
|
|
233
|
+
case "boolean":
|
|
234
|
+
return {externals: [], name: maybeNullable("Bool", nullable)}
|
|
235
|
+
case "number":
|
|
236
|
+
return {externals: [], name: maybeNullable("Int", nullable)}
|
|
237
|
+
case "bytes":
|
|
238
|
+
return {externals: [], name: maybeNullable("DataWrapper", nullable)}
|
|
239
|
+
case "void":
|
|
240
|
+
return {externals: [], name: maybeNullable("Void", nullable)}
|
|
241
|
+
default:
|
|
242
|
+
return {externals: [baseName], name: maybeNullable(baseName, nullable)}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function maybeNullable(name: string, nullable: boolean) {
|
|
247
|
+
return nullable ? `${name}?` : name
|
|
248
|
+
}
|