@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 ADDED
@@ -0,0 +1,137 @@
1
+ # licc - the little interprocess communications compiler
2
+
3
+ usage info: type `licc --help`
4
+
5
+ ## output
6
+
7
+ there are three kinds of json files `licc` understands. they are distinguished by their top-level `type` property.
8
+
9
+ ### structs
10
+
11
+ `"type": "struct"` definitions are simply generated into the output directory as a single source file of the
12
+ platform-appropriate language.
13
+
14
+ ### facades
15
+
16
+ `"type": "facade"` definitions can lead to several output files, depending on the definitions `senders` and `receivers`
17
+ fields:
18
+
19
+ * **senders and receivers**: the interface containing all methods
20
+ * **senders**: one implementation of the interface in form of the `SendDispatcher`.
21
+ It takes a transport instance that does the actual sending of the message and must be implemented manually.
22
+ * **receivers**: one `ReceiveDispatcher`. It takes the actual, working implementation of the interface during
23
+ construction
24
+ and dispatches to it.
25
+ * **additionally**, every platform that is on the receiving side of any facades gets one `GlobalDispatcher`
26
+ implementation
27
+ that dispatches to all receive dispatchers.
28
+
29
+ this leads to the following flow, with manually implemented components marked with `*`:
30
+
31
+ ```
32
+ SENDING SIDE: *caller* => SendDispatcher => *outgoing transport*
33
+ RECEIVING SIDE: *incoming transport* => GlobalDispatcher => ReceiveDispatcher => *facade implementation*
34
+ ```
35
+
36
+ Dispatch is achieved via string identifiers; the incoming transport will
37
+ call `GlobalDispatcher.dispatch("FacadeName", "methodName", arrayOfArgs)` which calls the ReceiveDispatcher
38
+ for `FacadeName` with `ReceiveDispatcher.dispatch("methodName", arrayOfArgs)`.
39
+ This call will be dispatched to the appropriate method as `facadeName.methodName(arrayOfArgs[0], ..., arrayOfArgs[-1])`.
40
+
41
+ ### typerefs
42
+
43
+ `"type": "typeref"` definitions are used to refer to types that are not defined in a definition file and are not
44
+ primitives (that
45
+ the generator therefore has no knowledge of). They have to contain a language-specific path to a definition of the
46
+ type that the generator will generate a reexport for, which can then be referred to by the facades (which don't actually
47
+ know the difference between a generated struct and such a reexport).
48
+
49
+ ## definition syntax
50
+
51
+ the schema format is described in `lib/common.ts`.
52
+ each schema is a JSON file with a single data type or facade definition.
53
+ as discussed above, the type (`struct`, `facade` or `typeref`) is given by the `type` property of the contained json
54
+ object.
55
+ facades must have a `senders` and a `receivers` property listing the appropriate platforms.
56
+
57
+ **Note:** there is minimal validation. we don't detect duplicate method or argument definitions and do not do a very
58
+ good job to validate type syntax.
59
+
60
+ ### structs
61
+
62
+ struct fields are given as an object with `"fieldName": "fieldType"` properties.
63
+
64
+ ```json
65
+ {
66
+ "name": "Foo",
67
+ "type": "struct",
68
+ "doc": "optional doc comment that explains the type's purpose",
69
+ "fields": {
70
+ "fieldName": "fieldType",
71
+ ...
72
+ }
73
+ }
74
+ ```
75
+
76
+ ### facades
77
+
78
+ ```json
79
+ {
80
+ "name": "BarFacade",
81
+ "type": "facade",
82
+ "senders": ["web"],
83
+ "receivers": ["desktop", "ios"],
84
+ "doc": "optional doc comment explaining the scope of the facade",
85
+ "methods": {
86
+ "methodName": {
87
+ "doc": "optional comment explaining the contract and purpose of the method",
88
+ "arg": [
89
+ {"argName1": "argType1"},
90
+ {"argName2": "argType2"},
91
+ ...
92
+ ],
93
+ "ret": "returnType"
94
+ },
95
+ ...
96
+ }
97
+ }
98
+ ```
99
+
100
+ Note: method arg must be given as a list of single-property objects as above to preserve argument order.
101
+
102
+ ### typerefs
103
+
104
+ ```json
105
+ {
106
+ "name": "BazType",
107
+ "type": "typeref",
108
+ "location": {
109
+ "typescript": "../../src/somedir/BazType.js",
110
+ "kotlin": "de.tutao.tutanota.BazType"
111
+ }
112
+ }
113
+ ```
114
+
115
+ Note the `.js` extension on the typescript reference. Reference paths are resolved relative to the `json` file
116
+ containing the typeref.
117
+
118
+ ### supported types:
119
+
120
+ * nullable types, denoted with a `?` suffix: `string?`
121
+ * `List<elementType>`
122
+ * `Map<keyType, valueType>`
123
+ * the primitives listed in `dist/parser.ts`
124
+ * "external" types (the ones that don't fit any of the above but are otherwise valid identifiers)
125
+ * any combination of these
126
+
127
+ all type names must be valid identifiers in all supported output languages.
128
+
129
+ ## Known issues
130
+
131
+ * the `bytes` primitive types is generating `DataWrapper` types for mobile, because we can't directly send byte arrays
132
+ over the bridge and need some kind of marker that distinguishes plain strings from encoded byte arrays. Currently,
133
+ this requirement to wrap byte arrays leaks out to consumers of the generated classes.
134
+ * struct definitions are generated for every language regardless if they're mentioned in that languages' generated
135
+ files.
136
+ * it's theoretically possible two separate compilations of the same source files to yield different output because field
137
+ order in json objects is not defined. this was not observed yet and is unlikely to become a problem.
package/cli.js ADDED
File without changes
@@ -0,0 +1,23 @@
1
+ const HEADER = "/* generated file, don't edit. */\n";
2
+ export class Accumulator {
3
+ appender;
4
+ code = "";
5
+ imports = new Set();
6
+ constructor(appender = (code) => this.code += code) {
7
+ this.appender = appender;
8
+ }
9
+ line(code = "") {
10
+ this.appender(code + "\n");
11
+ }
12
+ indent(indent = "\t") {
13
+ return new Accumulator((code) => {
14
+ this.appender(indent + code);
15
+ });
16
+ }
17
+ addImport(imp) {
18
+ this.imports.add(imp);
19
+ }
20
+ finish() {
21
+ return HEADER + "\n" + Array.from(this.imports).join("\n") + "\n" + this.code;
22
+ }
23
+ }
@@ -0,0 +1,224 @@
1
+ import { getArgs, minusculize } from "./common.js";
2
+ import { Accumulator } from "./Accumulator.js";
3
+ import { parseType } from "./Parser.js";
4
+ export class KotlinGenerator {
5
+ generateGlobalDispatcher(name, facadeNames) {
6
+ const acc = new Accumulator();
7
+ KotlinGenerator.generateImports(acc);
8
+ acc.line(`import de.tutao.tutanota.ipc.*`);
9
+ acc.line();
10
+ acc.line(`class ${name} (`);
11
+ const methodAcc = acc.indent();
12
+ methodAcc.line(`json: Json,`);
13
+ for (let facadeName of facadeNames) {
14
+ methodAcc.line(`${minusculize(facadeName)} : ${facadeName},`);
15
+ }
16
+ acc.line(") {");
17
+ for (let facadeName of facadeNames) {
18
+ methodAcc.line(`private val ${minusculize(facadeName)}: ${facadeName}ReceiveDispatcher = ${facadeName}ReceiveDispatcher(json, ${minusculize(facadeName)})`);
19
+ }
20
+ methodAcc.line();
21
+ methodAcc.line(`suspend fun dispatch(facadeName: String, methodName: String, args: List<String>): String {`);
22
+ const whenAcc = methodAcc.indent();
23
+ whenAcc.line(`return when (facadeName) {`);
24
+ const caseAcc = whenAcc.indent();
25
+ for (let facadeName of facadeNames) {
26
+ caseAcc.line(`"${facadeName}" -> this.${minusculize(facadeName)}.dispatch(methodName, args)`);
27
+ }
28
+ caseAcc.line(`else -> throw Error("unknown facade: $facadeName")`);
29
+ whenAcc.line(`}`);
30
+ methodAcc.line(`}`);
31
+ acc.line(`}`);
32
+ return acc.finish();
33
+ }
34
+ handleStructDefinition(definition) {
35
+ const acc = new Accumulator();
36
+ KotlinGenerator.generateImports(acc);
37
+ acc.line();
38
+ if (definition.doc) {
39
+ this.generateDocComment(acc, definition.doc);
40
+ }
41
+ acc.line("@Serializable");
42
+ acc.line(`data class ${definition.name}(`);
43
+ const fieldGenerator = acc.indent();
44
+ for (const [name, fieldDefinition] of Object.entries(definition.fields)) {
45
+ const renderedType = typeNameKotlin(fieldDefinition);
46
+ fieldGenerator.line(`val ${name}: ${renderedType.name},`);
47
+ }
48
+ acc.line(")");
49
+ return acc.finish();
50
+ }
51
+ static generateImports(acc) {
52
+ acc.line("package de.tutao.tutanota.ipc");
53
+ acc.line();
54
+ acc.line("import kotlinx.serialization.*");
55
+ acc.line("import kotlinx.serialization.json.*");
56
+ acc.line();
57
+ }
58
+ generateDocComment(acc, comment) {
59
+ if (!comment)
60
+ return;
61
+ acc.line("/**");
62
+ acc.line(` * ${comment}`);
63
+ acc.line(" */");
64
+ }
65
+ generateFacade(definition) {
66
+ const acc = new Accumulator();
67
+ KotlinGenerator.generateImports(acc);
68
+ this.generateDocComment(acc, definition.doc);
69
+ acc.line(`interface ${definition.name} {`);
70
+ const methodAcc = acc.indent();
71
+ for (const [name, methodDefinition] of Object.entries(definition.methods)) {
72
+ this.generateDocComment(methodAcc, methodDefinition.doc);
73
+ KotlinGenerator.generateMethodSignature(methodAcc, name, methodDefinition);
74
+ }
75
+ acc.line("}");
76
+ return acc.finish();
77
+ }
78
+ generateReceiveDispatcher(definition) {
79
+ const acc = new Accumulator();
80
+ // Some names might clash, we don't read this file, we don't care
81
+ acc.line(`@file:Suppress("NAME_SHADOWING")`);
82
+ KotlinGenerator.generateImports(acc);
83
+ acc.line(`class ${definition.name}ReceiveDispatcher(`);
84
+ acc.indent().line(`private val json: Json,`);
85
+ acc.indent().line(`private val facade: ${definition.name},`);
86
+ acc.line(`) {`);
87
+ const methAcc = acc.indent();
88
+ methAcc.line();
89
+ methAcc.line(`suspend fun dispatch(method: String, arg: List<String>): String {`);
90
+ const whenAcc = methAcc.indent();
91
+ whenAcc.line(`when (method) {`);
92
+ const caseAcc = whenAcc.indent();
93
+ for (const [methodName, methodDef] of Object.entries(definition.methods)) {
94
+ caseAcc.line(`"${methodName}" -> {`);
95
+ const arg = getArgs(methodName, methodDef);
96
+ const decodedArgs = [];
97
+ for (let i = 0; i < arg.length; i++) {
98
+ const { name: argName, type } = arg[i];
99
+ const renderedArgType = typeNameKotlin(type);
100
+ decodedArgs.push([argName, renderedArgType]);
101
+ }
102
+ const varAcc = caseAcc.indent();
103
+ for (let i = 0; i < arg.length; i++) {
104
+ const [argName, renderedType] = decodedArgs[i];
105
+ varAcc.line(`val ${argName}: ${renderedType.name} = json.decodeFromString(arg[${i}])`);
106
+ }
107
+ varAcc.line(`val result: ${typeNameKotlin(methodDef.ret).name} = this.facade.${methodName}(`);
108
+ for (let i = 0; i < arg.length; i++) {
109
+ const [argName] = decodedArgs[i];
110
+ varAcc.indent().line(`${argName},`);
111
+ }
112
+ varAcc.line(`)`);
113
+ varAcc.line(`return json.encodeToString(result)`);
114
+ caseAcc.line(`}`);
115
+ }
116
+ caseAcc.line(`else -> throw Error("unknown method for ${definition.name}: $method")`);
117
+ whenAcc.line(`}`);
118
+ methAcc.line(`}`);
119
+ acc.line(`}`);
120
+ return acc.finish();
121
+ }
122
+ static generateNativeInterface() {
123
+ const acc = new Accumulator();
124
+ KotlinGenerator.generateImports(acc);
125
+ acc.line(`interface NativeInterface {`);
126
+ acc.indent().line(`suspend fun sendRequest(requestType: String, args: List<String>): String`);
127
+ acc.line(`}`);
128
+ return acc.finish();
129
+ }
130
+ static generateMethodSignature(methodGenerator, name, methodDefinition, prefix = "") {
131
+ methodGenerator.line(`${prefix} suspend fun ${name}(`);
132
+ const argGenerator = methodGenerator.indent();
133
+ for (const argument of getArgs(name, methodDefinition)) {
134
+ const renderedArgument = typeNameKotlin(argument.type);
135
+ argGenerator.line(`${argument.name}: ${renderedArgument.name},`);
136
+ }
137
+ const renderedReturn = typeNameKotlin(methodDefinition.ret);
138
+ methodGenerator.line(`): ${renderedReturn.name}`);
139
+ }
140
+ generateSendDispatcher(definition) {
141
+ const acc = new Accumulator();
142
+ KotlinGenerator.generateImports(acc);
143
+ const classBodyAcc = acc.indent();
144
+ acc.line(`class ${definition.name}SendDispatcher (`);
145
+ classBodyAcc.line(`private val json: Json,`);
146
+ classBodyAcc.line(`private val transport : NativeInterface,`);
147
+ acc.line(`) : ${definition.name} {`);
148
+ classBodyAcc.line(`private val encodedFacade = json.encodeToString("${definition.name}")`);
149
+ for (const [methodName, methodDefinition] of Object.entries(definition.methods)) {
150
+ KotlinGenerator.generateMethodSignature(classBodyAcc, methodName, methodDefinition, "override");
151
+ classBodyAcc.line("{");
152
+ const methodBodyAcc = classBodyAcc.indent();
153
+ methodBodyAcc.line(`val encodedMethod = json.encodeToString("${methodName}")`);
154
+ methodBodyAcc.line("val args : MutableList<String> = mutableListOf()");
155
+ for (let arg of getArgs(methodName, methodDefinition)) {
156
+ methodBodyAcc.line(`args.add(json.encodeToString(${arg.name}))`);
157
+ }
158
+ if (methodDefinition.ret !== "void") {
159
+ methodBodyAcc.line(`val result = this.transport.sendRequest("ipc", listOf(encodedFacade, encodedMethod) + args)`);
160
+ methodBodyAcc.line(`return json.decodeFromString(result)`);
161
+ }
162
+ else {
163
+ methodBodyAcc.line(`this.transport.sendRequest("ipc", listOf(encodedFacade, encodedMethod) + args)`);
164
+ }
165
+ classBodyAcc.line(`}`);
166
+ classBodyAcc.line();
167
+ }
168
+ acc.line(`}`);
169
+ return acc.finish();
170
+ }
171
+ generateExtraFiles() {
172
+ return {
173
+ "NativeInterface": KotlinGenerator.generateNativeInterface()
174
+ };
175
+ }
176
+ generateTypeRef(outDir, definitionPath, definition) {
177
+ if (definition.location.kotlin) {
178
+ const acc = new Accumulator();
179
+ acc.line(`package de.tutao.tutanota.ipc`);
180
+ acc.line(`typealias ${definition.name} = ${definition.location.kotlin}`);
181
+ return acc.finish();
182
+ }
183
+ else {
184
+ return null;
185
+ }
186
+ }
187
+ }
188
+ function typeNameKotlin(name) {
189
+ const parsed = parseType(name);
190
+ return renderKotlinType(parsed);
191
+ }
192
+ function renderKotlinType(parsed) {
193
+ const { baseName, nullable, external } = parsed;
194
+ switch (baseName) {
195
+ case "List":
196
+ const renderedListInner = renderKotlinType(parsed.generics[0]);
197
+ return {
198
+ externals: renderedListInner.externals,
199
+ name: maybeNullable(`List<${renderedListInner.name}>`, nullable)
200
+ };
201
+ case "Map":
202
+ const renderedKey = renderKotlinType(parsed.generics[0]);
203
+ const renderedValue = renderKotlinType(parsed.generics[1]);
204
+ return {
205
+ externals: [...renderedKey.externals, ...renderedValue.externals],
206
+ name: maybeNullable(`Map<${renderedKey.name}, ${renderedValue.name}>`, nullable)
207
+ };
208
+ case "string":
209
+ return { externals: [], name: maybeNullable("String", nullable) };
210
+ case "boolean":
211
+ return { externals: [], name: maybeNullable("Boolean", nullable) };
212
+ case "number":
213
+ return { externals: [], name: maybeNullable("Int", nullable) };
214
+ case "bytes":
215
+ return { externals: [], name: maybeNullable("DataWrapper", nullable) };
216
+ case "void":
217
+ return { externals: [], name: maybeNullable("Unit", nullable) };
218
+ default:
219
+ return { externals: [baseName], name: maybeNullable(baseName, nullable) };
220
+ }
221
+ }
222
+ function maybeNullable(name, nullable) {
223
+ return nullable ? `${name}?` : name;
224
+ }
package/dist/Parser.js ADDED
@@ -0,0 +1,49 @@
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
+ const FORBIDDEN_IDENTIFIERS = new Set([...KOTLIN_KEYWORDS, ...TYPESCRIPT_KEYWORDS, ...SWIFT_KEYWORDS]);
19
+ /**
20
+ * parse a type definition string from the json into a structure that contains all information needed to render
21
+ * language-specific type defs
22
+ */
23
+ export function parseType(typeString) {
24
+ let nullable = false;
25
+ typeString = typeString.trim();
26
+ if (typeString.endsWith("?")) {
27
+ nullable = true;
28
+ typeString = typeString.slice(0, -1);
29
+ }
30
+ const listMatch = typeString.match(/^\s*List<\s*(.*)\s*>\s*$/);
31
+ if (listMatch) {
32
+ const nested = parseType(listMatch[1]);
33
+ return { baseName: "List", generics: [nested], nullable, external: false };
34
+ }
35
+ const mapMatch = typeString.match(/^\s*Map<\s*(.*?),\s*(.*?)\s*>\s*$/);
36
+ if (mapMatch) {
37
+ const nestedKey = parseType(mapMatch[1]);
38
+ const nestedValue = parseType(mapMatch[2]);
39
+ return { baseName: "Map", generics: [nestedKey, nestedValue], nullable, external: false };
40
+ }
41
+ // this is a basic type without generic params
42
+ const external = !PRIMITIVES.includes(typeString);
43
+ const willBreakAtLeastOneLang = FORBIDDEN_IDENTIFIERS.has(typeString) && external;
44
+ const startsWithLetterOrUnderscore = typeString.match(/^[_a-zA-Z]/);
45
+ if (willBreakAtLeastOneLang || !startsWithLetterOrUnderscore) {
46
+ throw new Error(`illegal identifier: "${typeString}"`);
47
+ }
48
+ return { baseName: typeString, generics: [], external, nullable };
49
+ } // frontend type
@@ -0,0 +1,232 @@
1
+ import { getArgs, minusculize } from "./common.js";
2
+ import { Accumulator } from "./Accumulator.js";
3
+ import { parseType } from "./Parser.js";
4
+ export class SwiftGenerator {
5
+ handleStructDefinition(definition) {
6
+ const acc = new Accumulator();
7
+ this.generateDocComment(acc, definition.doc);
8
+ acc.line(`public struct ${definition.name} : Codable {`);
9
+ const fieldGenerator = acc.indent();
10
+ for (const [name, fieldDefinition] of Object.entries(definition.fields)) {
11
+ const renderedType = typeNameSwift(fieldDefinition);
12
+ fieldGenerator.line(`let ${name}: ${renderedType.name}`);
13
+ }
14
+ acc.line("}");
15
+ return acc.finish();
16
+ }
17
+ generateDocComment(acc, comment) {
18
+ if (!comment)
19
+ return;
20
+ acc.line("/**");
21
+ acc.line(` * ${comment}`);
22
+ acc.line(" */");
23
+ }
24
+ generateFacade(definition) {
25
+ const acc = new Accumulator();
26
+ acc.line("import Foundation");
27
+ acc.line();
28
+ this.generateDocComment(acc, definition.doc);
29
+ acc.line(`public protocol ${definition.name} {`);
30
+ const methodAcc = acc.indent();
31
+ for (const [name, methodDefinition] of Object.entries(definition.methods)) {
32
+ this.generateDocComment(methodAcc, methodDefinition.doc);
33
+ SwiftGenerator.generateMethodSignature(methodAcc, name, methodDefinition);
34
+ }
35
+ acc.line("}");
36
+ return acc.finish();
37
+ }
38
+ static generateMethodSignature(methodGenerator, name, methodDefinition) {
39
+ methodGenerator.line(`func ${name}(`);
40
+ const argGenerator = methodGenerator.indent();
41
+ const args = getArgs(name, methodDefinition);
42
+ const lastArg = args[args.length - 1];
43
+ for (const argument of args) {
44
+ const renderedArgument = typeNameSwift(argument.type);
45
+ const argLine = `_ ${argument.name}: ${renderedArgument.name}` +
46
+ ((argument === lastArg) ? "" : ",");
47
+ argGenerator.line(argLine);
48
+ }
49
+ const renderedReturn = typeNameSwift(methodDefinition.ret);
50
+ methodGenerator.line(`) async throws -> ${renderedReturn.name}`);
51
+ }
52
+ generateReceiveDispatcher(definition) {
53
+ const acc = new Accumulator();
54
+ acc.line("import Foundation");
55
+ acc.line();
56
+ acc.line(`public class ${definition.name}ReceiveDispatcher {`);
57
+ const methodAcc = acc.indent();
58
+ methodAcc.line(`let facade: ${definition.name}`);
59
+ methodAcc.line(`init(facade: ${definition.name}) {`);
60
+ methodAcc.indent().line(`self.facade = facade`);
61
+ methodAcc.line(`}`);
62
+ methodAcc.line(`func dispatch(method: String, arg: [String]) async throws -> String {`);
63
+ const switchAcc = methodAcc.indent();
64
+ switchAcc.line(`switch method {`);
65
+ const caseAcc = switchAcc.indent();
66
+ for (const [methodName, method] of Object.entries(definition.methods)) {
67
+ const arg = getArgs(methodName, method);
68
+ const decodedArgs = [];
69
+ for (let i = 0; i < arg.length; i++) {
70
+ const { name: argName, type } = arg[i];
71
+ const renderedArgType = typeNameSwift(type);
72
+ decodedArgs.push([argName, renderedArgType]);
73
+ }
74
+ switchAcc.line(`case "${methodName}":`);
75
+ for (let i = 0; i < arg.length; i++) {
76
+ const [argName, argType] = decodedArgs[i];
77
+ caseAcc.line(`let ${argName} = try! JSONDecoder().decode(${argType.name}.self, from: arg[${i}].data(using: .utf8)!)`);
78
+ }
79
+ if (method.ret === "void") {
80
+ caseAcc.line(`try await self.facade.${methodName}(`);
81
+ }
82
+ else {
83
+ caseAcc.line(`let result = try await self.facade.${methodName}(`);
84
+ }
85
+ for (let i = 0; i < arg.length; i++) {
86
+ const comma = i === arg.length - 1 ? "" : ",";
87
+ caseAcc.indent().line(arg[i].name + comma);
88
+ }
89
+ caseAcc.line(")");
90
+ if (method.ret === "void") {
91
+ caseAcc.line(`return "null"`);
92
+ }
93
+ else {
94
+ caseAcc.line(`return toJson(result)`);
95
+ }
96
+ }
97
+ switchAcc.line(`default:`);
98
+ caseAcc.line(`fatalError("licc messed up! \\(method)")`);
99
+ switchAcc.line(`}`);
100
+ methodAcc.line(`}`);
101
+ acc.line(`}`);
102
+ return acc.finish();
103
+ }
104
+ generateGlobalDispatcher(name, facadeNames) {
105
+ const acc = new Accumulator();
106
+ acc.line(`public class ${name} {`);
107
+ const methodAcc = acc.indent();
108
+ for (let facadeName of facadeNames) {
109
+ methodAcc.line(`private let ${minusculize(facadeName)}: ${facadeName}ReceiveDispatcher`);
110
+ }
111
+ methodAcc.line();
112
+ methodAcc.line(`init(`);
113
+ const lastFacadeName = facadeNames[facadeNames.length - 1];
114
+ for (let facadeName of facadeNames) {
115
+ const comma = facadeName === lastFacadeName ? "" : ",";
116
+ methodAcc.indent().line(`${minusculize(facadeName)} : ${facadeName}${comma}`);
117
+ }
118
+ methodAcc.line(`) {`);
119
+ for (let facadeName of facadeNames) {
120
+ methodAcc.indent().line(`self.${minusculize(facadeName)} = ${facadeName}ReceiveDispatcher(facade: ${minusculize(facadeName)})`);
121
+ }
122
+ methodAcc.line(`}`);
123
+ methodAcc.line();
124
+ methodAcc.line(`func dispatch(facadeName: String, methodName: String, args: Array<String>) async throws -> String {`);
125
+ const switchAcc = methodAcc.indent();
126
+ switchAcc.line(`switch facadeName {`);
127
+ const caseAcc = switchAcc.indent();
128
+ for (let facadeName of facadeNames) {
129
+ caseAcc.line(`case "${facadeName}":`);
130
+ caseAcc.indent().line(`return try await self.${minusculize(facadeName)}.dispatch(method: methodName, arg: args)`);
131
+ }
132
+ caseAcc.line(`default:`);
133
+ caseAcc.indent().line(`fatalError("licc messed up! " + facadeName)`);
134
+ switchAcc.line(`}`);
135
+ methodAcc.line(`}`);
136
+ acc.line(`}`);
137
+ return acc.finish();
138
+ }
139
+ generateSendDispatcher(definition) {
140
+ const acc = new Accumulator();
141
+ acc.line("import Foundation");
142
+ acc.line();
143
+ acc.line(`class ${definition.name}SendDispatcher : ${definition.name} {`);
144
+ const classBodyAcc = acc.indent();
145
+ classBodyAcc.line(`private let transport: NativeInterface`);
146
+ classBodyAcc.line(`init(transport: NativeInterface) { self.transport = transport }`);
147
+ classBodyAcc.line();
148
+ for (const [methodName, methodDefinition] of Object.entries(definition.methods)) {
149
+ SwiftGenerator.generateMethodSignature(classBodyAcc, methodName, methodDefinition);
150
+ const methodBodyAcc = classBodyAcc.indent();
151
+ methodBodyAcc.line("{");
152
+ const args = getArgs(methodName, methodDefinition);
153
+ if (args.length > 0) {
154
+ methodBodyAcc.line("var args = [String]()");
155
+ }
156
+ else {
157
+ // let instead of var so that it shuts up about mutability unused
158
+ methodBodyAcc.line("let args = [String]()");
159
+ }
160
+ for (let arg of args) {
161
+ methodBodyAcc.line(`args.append(toJson(${arg.name}))`);
162
+ }
163
+ methodBodyAcc.line(`let encodedFacadeName = toJson("${definition.name}")`);
164
+ methodBodyAcc.line(`let encodedMethodName = toJson("${methodName}")`);
165
+ if (methodDefinition.ret !== "void") {
166
+ methodBodyAcc.line(`let returnValue = try await self.transport.sendRequest(requestType: "ipc", args: [encodedFacadeName, encodedMethodName] + args)`);
167
+ methodBodyAcc.line(`return try! JSONDecoder().decode(${typeNameSwift(methodDefinition.ret).name}.self, from: returnValue.data(using: .utf8)!)`);
168
+ }
169
+ else {
170
+ methodBodyAcc.line(`let _ = try await self.transport.sendRequest(requestType: "ipc", args: [encodedFacadeName, encodedMethodName] + args)`);
171
+ }
172
+ methodBodyAcc.line(`}`);
173
+ classBodyAcc.line();
174
+ }
175
+ acc.line(`}`);
176
+ return acc.finish();
177
+ }
178
+ generateExtraFiles() {
179
+ return {
180
+ "NativeInterface": SwiftGenerator.generateNativeInterface()
181
+ };
182
+ }
183
+ static generateNativeInterface() {
184
+ const acc = new Accumulator();
185
+ acc.line(`protocol NativeInterface {`);
186
+ acc.indent().line(`func sendRequest(requestType: String, args: [String]) async throws -> String`);
187
+ acc.line(`}`);
188
+ acc.line();
189
+ acc.line("func toJson<T>(_ thing: T) -> String where T : Encodable {");
190
+ acc.indent().line("return String(data: try! JSONEncoder().encode(thing), encoding: .utf8)!");
191
+ acc.line("}");
192
+ acc.line();
193
+ return acc.finish();
194
+ }
195
+ generateTypeRef(outDir, definitionPath, definition) {
196
+ return null;
197
+ }
198
+ }
199
+ function typeNameSwift(name) {
200
+ const parsed = parseType(name);
201
+ return renderSwiftType(parsed);
202
+ }
203
+ function renderSwiftType(parsed) {
204
+ const { baseName, nullable } = parsed;
205
+ switch (baseName) {
206
+ case "List":
207
+ const renderedListInner = renderSwiftType(parsed.generics[0]);
208
+ return { externals: renderedListInner.externals, name: maybeNullable(`[${renderedListInner.name}]`, nullable) };
209
+ case "Map":
210
+ const renderedKey = renderSwiftType(parsed.generics[0]);
211
+ const renderedValue = renderSwiftType(parsed.generics[1]);
212
+ return {
213
+ externals: [...renderedKey.externals, ...renderedValue.externals],
214
+ name: maybeNullable(`[${renderedKey.name} : ${renderedValue.name}]`, nullable)
215
+ };
216
+ case "string":
217
+ return { externals: [], name: maybeNullable("String", nullable) };
218
+ case "boolean":
219
+ return { externals: [], name: maybeNullable("Bool", nullable) };
220
+ case "number":
221
+ return { externals: [], name: maybeNullable("Int", nullable) };
222
+ case "bytes":
223
+ return { externals: [], name: maybeNullable("DataWrapper", nullable) };
224
+ case "void":
225
+ return { externals: [], name: maybeNullable("Void", nullable) };
226
+ default:
227
+ return { externals: [baseName], name: maybeNullable(baseName, nullable) };
228
+ }
229
+ }
230
+ function maybeNullable(name, nullable) {
231
+ return nullable ? `${name}?` : name;
232
+ }