skir-kotlin-gen 0.0.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/dist/index.js ADDED
@@ -0,0 +1,671 @@
1
+ // TODO: add comments
2
+ import { convertCase, simpleHash, } from "skir-internal";
3
+ import { z } from "zod";
4
+ import { Namer, toEnumConstantName } from "./naming.js";
5
+ import { TypeSpeller } from "./type_speller.js";
6
+ const Config = z.object({
7
+ packagePrefix: z
8
+ .string()
9
+ .regex(/^([a-z_$][a-z0-9_$]*\.)*$/)
10
+ .optional(),
11
+ });
12
+ class KotlinCodeGenerator {
13
+ constructor() {
14
+ this.id = "kotlin";
15
+ this.configType = Config;
16
+ this.version = "1.0.0";
17
+ }
18
+ generateCode(input) {
19
+ const { recordMap, config } = input;
20
+ const outputFiles = [];
21
+ for (const module of input.modules) {
22
+ outputFiles.push({
23
+ path: module.path.replace(/\.skir$/, ".kt"),
24
+ code: new KotlinSourceFileGenerator(module, recordMap, config).generate(),
25
+ });
26
+ }
27
+ return { files: outputFiles };
28
+ }
29
+ }
30
+ // Generates the code for one Kotlin file.
31
+ class KotlinSourceFileGenerator {
32
+ constructor(inModule, recordMap, config) {
33
+ this.inModule = inModule;
34
+ this.code = "";
35
+ this.packagePrefix = config.packagePrefix ?? "";
36
+ this.namer = new Namer(this.packagePrefix);
37
+ this.typeSpeller = new TypeSpeller(recordMap, this.namer);
38
+ }
39
+ generate() {
40
+ // http://patorjk.com/software/taag/#f=Doom&t=Do%20not%20edit
41
+ this.push(`@file:Suppress("ktlint")
42
+
43
+ // ______ _ _ _ _
44
+ // | _ \\ | | | |(_)| |
45
+ // | | | | ___ _ __ ___ | |_ ___ __| | _ | |_
46
+ // | | | | / _ \\ | '_ \\ / _ \\ | __| / _ \\ / _\` || || __|
47
+ // | |/ / | (_) | | | | || (_) || |_ | __/| (_| || || |_
48
+ // |___/ \\___/ |_| |_| \\___/ \\__| \\___| \\__,_||_| \\__|
49
+ //
50
+
51
+ // To install the Soia client library, add:
52
+ // implementation("build.skir:skir-client:latest.release")
53
+ // to your build.gradle.kts file
54
+
55
+ `, `package ${this.packagePrefix}skirout.`, this.inModule.path.replace(/\.skir$/, "").replace("/", "."), ";\n\n", "import build.skir.internal.MustNameArguments as _MustNameArguments;\n", "import build.skir.internal.UnrecognizedFields as _UnrecognizedFields;\n", "import build.skir.internal.UnrecognizedVariant as _UnrecognizedVariant;\n\n");
56
+ this.writeClassesForRecords(this.inModule.records.filter(
57
+ // Only retain top-level records.
58
+ // Nested records will be processed from within their ancestors.
59
+ (r) => r.recordAncestors.length === 1));
60
+ for (const method of this.inModule.methods) {
61
+ this.writeMethod(method);
62
+ }
63
+ for (const constant of this.inModule.constants) {
64
+ this.writeConstant(constant);
65
+ }
66
+ return this.joinLinesAndFixFormatting();
67
+ }
68
+ writeClassesForRecords(recordLocations) {
69
+ for (const record of recordLocations) {
70
+ const { recordType } = record.record;
71
+ this.pushEol();
72
+ if (recordType === "struct") {
73
+ this.writeClassesForStruct(record);
74
+ }
75
+ else {
76
+ this.writeClassForEnum(record);
77
+ }
78
+ }
79
+ }
80
+ writeClassesForStruct(struct) {
81
+ const { namer, typeSpeller } = this;
82
+ const { recordMap } = typeSpeller;
83
+ const { fields } = struct.record;
84
+ const className = namer.getClassName(struct);
85
+ const { qualifiedName } = className;
86
+ this.push(`sealed interface ${className.name}_OrMutable {\n`);
87
+ for (const field of fields) {
88
+ const fieldName = namer.structFieldToKotlinName(field);
89
+ const allRecordsFrozen = field.isRecursive === "hard";
90
+ const type = typeSpeller.getKotlinType(field.type, "maybe-mutable", allRecordsFrozen);
91
+ this.push(commentify(docToCommentText(field.doc)));
92
+ this.push(`val ${fieldName}: ${type};\n`);
93
+ }
94
+ this.push(`\nfun toFrozen(): ${qualifiedName};\n`);
95
+ this.push("}\n\n", // class _OrMutable
96
+ commentify([docToCommentText(struct.record.doc), "\nDeeply immutable."]), '@kotlin.Suppress("UNUSED_PARAMETER")\n', `class ${className.name} private constructor(\n`);
97
+ for (const field of fields) {
98
+ const fieldName = namer.structFieldToKotlinName(field);
99
+ const type = typeSpeller.getKotlinType(field.type, "frozen");
100
+ if (field.isRecursive === "hard") {
101
+ this.push(`private val __${fieldName}: ${type}?,\n`);
102
+ }
103
+ else {
104
+ this.push(`override val ${fieldName}: ${type},\n`);
105
+ }
106
+ }
107
+ this.push(`private val _unrecognizedFields: _UnrecognizedFields<${qualifiedName}>? =\n`, "null,\n", `): ${qualifiedName}_OrMutable {\n`);
108
+ for (const field of fields) {
109
+ if (field.isRecursive === "hard") {
110
+ const fieldName = namer.structFieldToKotlinName(field);
111
+ const defaultExpr = this.getDefaultExpression(field.type);
112
+ this.push(`override val ${fieldName} get() = __${fieldName} ?: ${defaultExpr};\n`);
113
+ }
114
+ }
115
+ this.pushEol();
116
+ this.push("constructor(\n", "_mustNameArguments: _MustNameArguments =\n_MustNameArguments,\n");
117
+ for (const field of fields) {
118
+ const fieldName = namer.structFieldToKotlinName(field);
119
+ const type = typeSpeller.getKotlinType(field.type, "initializer");
120
+ this.push(`${fieldName}: ${type},\n`);
121
+ }
122
+ this.push(`_unrecognizedFields: _UnrecognizedFields<${qualifiedName}>? =\n`, "null,\n", "): this(\n");
123
+ for (const field of fields) {
124
+ const fieldName = namer.structFieldToKotlinName(field);
125
+ this.push(this.toFrozenExpression(fieldName, field.type), ",\n");
126
+ }
127
+ this.push("_unrecognizedFields,\n", ") {}\n\n", '@kotlin.Deprecated("Already frozen", kotlin.ReplaceWith("this"))\n', "override fun toFrozen() = this;\n\n", "/** Returns a mutable shallow copy of this instance */\n", `fun toMutable() = Mutable(\n`);
128
+ for (const field of fields) {
129
+ const fieldName = namer.structFieldToKotlinName(field);
130
+ this.push(`${fieldName} = this.${fieldName},\n`);
131
+ }
132
+ this.push(");\n\n");
133
+ if (fields.length) {
134
+ this.push("/** Returns a shallow copy of this instance with the specified fields replaced. */\n", "fun copy(\n", "_mustNameArguments: _MustNameArguments =\n_MustNameArguments,\n");
135
+ for (const field of fields) {
136
+ const fieldName = namer.structFieldToKotlinName(field);
137
+ const type = typeSpeller.getKotlinType(field.type, "initializer");
138
+ this.push(`${fieldName}: ${type} =\nthis.${fieldName},\n`);
139
+ }
140
+ this.push(`) = ${qualifiedName}(\n`);
141
+ for (const field of fields) {
142
+ const fieldName = namer.structFieldToKotlinName(field);
143
+ this.push(this.toFrozenExpression(fieldName, field.type), ",\n");
144
+ }
145
+ this.push("this._unrecognizedFields,\n", ");\n\n", '@kotlin.Deprecated("No point in creating an exact copy of an immutable object", kotlin.ReplaceWith("this"))\n', "fun copy() = this;\n\n");
146
+ }
147
+ this.push("override fun equals(other: kotlin.Any?): kotlin.Boolean {\n", `return this === other || (other is ${qualifiedName}`, fields
148
+ .map((f) => ` && this.${namer.structFieldToKotlinName(f)} == other.${namer.structFieldToKotlinName(f)}`)
149
+ .join(""), ");\n", "}\n\n", "override fun hashCode(): kotlin.Int {\n", "return kotlin.collections.listOf<kotlin.Any?>(", fields.map((f) => `this.${namer.structFieldToKotlinName(f)}`).join(", "), ").hashCode();\n", "}\n\n", "override fun toString(): kotlin.String {\n", "return build.skir.internal.toStringImpl(\n", "this,\n", `${qualifiedName}.serializerImpl,\n`, ")\n", "}\n\n");
150
+ this.push(`/** Mutable version of [${className.name}]. */\n`, `class Mutable internal constructor(\n`, "_mustNameArguments: _MustNameArguments =\n_MustNameArguments,\n");
151
+ for (const field of fields) {
152
+ const fieldName = namer.structFieldToKotlinName(field);
153
+ const allRecordsFrozen = !!field.isRecursive;
154
+ const type = typeSpeller.getKotlinType(field.type, "maybe-mutable", allRecordsFrozen);
155
+ const defaultExpr = this.getDefaultExpression(field.type);
156
+ this.push(`override var ${fieldName}: ${type} =\n${defaultExpr},\n`);
157
+ }
158
+ this.push(`internal var _unrecognizedFields: _UnrecognizedFields<${qualifiedName}>? =\n`, "null,\n", `): ${qualifiedName}_OrMutable {\n`, "/** Returns a deeply immutable copy of this instance */\n", `override fun toFrozen() = ${qualifiedName}(\n`);
159
+ for (const field of fields) {
160
+ const fieldName = namer.structFieldToKotlinName(field);
161
+ this.push(`${fieldName} = this.${fieldName},\n`);
162
+ }
163
+ this.push("_unrecognizedFields = this._unrecognizedFields,\n", //
164
+ `);\n\n`);
165
+ this.writeMutableGetters(fields);
166
+ this.push("}\n\n", "companion object {\n", "private val default =\n", `${qualifiedName}(\n`);
167
+ for (const field of fields) {
168
+ this.push(field.isRecursive === "hard"
169
+ ? "null"
170
+ : this.getDefaultExpression(field.type), ",\n");
171
+ }
172
+ this.push(");\n\n", "/** Returns an instance with all fields set to their default values. */\n", "fun partial() = default;\n\n", "/**\n", ` * Creates a new instance of [${className.name}].\n`, " * Unlike the constructor, does not require all fields to be specified.\n", " * Missing fields will be set to their default values.\n", " */\n", "fun partial(\n", "_mustNameArguments: _MustNameArguments =\n_MustNameArguments,\n");
173
+ for (const field of fields) {
174
+ const fieldName = namer.structFieldToKotlinName(field);
175
+ const type = typeSpeller.getKotlinType(field.type, "initializer");
176
+ const defaultExpr = this.getDefaultExpression(field.type);
177
+ this.push(`${fieldName}: ${type} =\n${defaultExpr},\n`);
178
+ }
179
+ this.push(`) = ${qualifiedName}(\n`);
180
+ for (const field of fields) {
181
+ const fieldName = namer.structFieldToKotlinName(field);
182
+ this.push(`${fieldName} = ${fieldName},\n`);
183
+ }
184
+ this.push("_unrecognizedFields = null,\n", ");\n\n", "private val serializerImpl = build.skir.internal.StructSerializer(\n", `recordId = "${getRecordId(struct)}",\n`, `doc = ${toKotlinStringLiteral(struct.record.doc.text)},\n`, "defaultInstance = default,\n", "newMutableFn = { it?.toMutable() ?: Mutable() },\n", "toFrozenFn = { it.toFrozen() },\n", "getUnrecognizedFields = { it._unrecognizedFields },\n", "setUnrecognizedFields = { m, u -> m._unrecognizedFields = u },\n", ");\n\n", `/** Serializer for [${className.name}] instances. */\n`, "val serializer = build.skir.internal.makeSerializer(serializerImpl);\n\n", `/** Describes the [${className.name}] type. Provides runtime introspection capabilities. */\n`, "val typeDescriptor get() = serializerImpl.typeDescriptor;\n\n", "init {\n");
185
+ for (const field of fields) {
186
+ const fieldName = namer.structFieldToKotlinName(field);
187
+ this.push("serializerImpl.addField(\n", `"${field.name.text}",\n`, `"${fieldName}",\n`, `${field.number},\n`, `${typeSpeller.getSerializerExpression(field.type)},\n`, `${toKotlinStringLiteral(field.doc.text)},\n`, `{ it.${fieldName} },\n`, `{ mut, v -> mut.${fieldName} = v },\n`, ");\n");
188
+ }
189
+ for (const removedNumber of struct.record.removedNumbers) {
190
+ this.push(`serializerImpl.addRemovedNumber(${removedNumber});\n`);
191
+ }
192
+ this.push("serializerImpl.finalizeStruct();\n", "}\n", "}\n");
193
+ // Write the classes for the records nested in `record`.
194
+ const nestedRecords = struct.record.nestedRecords.map((r) => recordMap.get(r.key));
195
+ this.writeClassesForRecords(nestedRecords);
196
+ this.push("}\n\n");
197
+ }
198
+ writeMutableGetters(fields) {
199
+ const { namer, typeSpeller } = this;
200
+ for (const field of fields) {
201
+ if (field.isRecursive) {
202
+ continue;
203
+ }
204
+ const type = field.type;
205
+ const fieldName = namer.structFieldToKotlinName(field);
206
+ const mutableGetterName = "mutable" + convertCase(field.name.text, "UpperCamel");
207
+ const mutableType = typeSpeller.getKotlinType(field.type, "mutable");
208
+ const accessor = `this.${fieldName}`;
209
+ let bodyLines = [];
210
+ if (type.kind === "array") {
211
+ bodyLines = [
212
+ "return when (value) {\n",
213
+ "is build.skir.internal.MutableList -> value;\n",
214
+ "else -> {\n",
215
+ "value = build.skir.internal.MutableList(value);\n",
216
+ `${accessor} = value;\n`,
217
+ "value;\n",
218
+ "}\n",
219
+ "}\n",
220
+ ];
221
+ }
222
+ else if (type.kind === "record") {
223
+ const record = this.typeSpeller.recordMap.get(type.key);
224
+ if (record.record.recordType === "struct") {
225
+ const structQualifiedName = namer.getClassName(record).qualifiedName;
226
+ bodyLines = [
227
+ "return when (value) {\n",
228
+ `is ${structQualifiedName} -> {\n`,
229
+ "value = value.toMutable();\n",
230
+ `${accessor} = value;\n`,
231
+ "return value;\n",
232
+ "}\n",
233
+ `is ${structQualifiedName}.Mutable -> value;\n`,
234
+ "}\n",
235
+ ];
236
+ }
237
+ }
238
+ if (bodyLines.length) {
239
+ this.push("/**\n", ` * If the value of [${fieldName}] is already mutable, returns it as-is.\n`, ` * Otherwise, makes a mutable copy, assigns it back to [${fieldName}] and returns it.\n`, ` */\n`, `val ${mutableGetterName}: ${mutableType} get() {\n`, `var value = ${accessor};\n`);
240
+ for (const line of bodyLines) {
241
+ this.push(line);
242
+ }
243
+ this.push("}\n\n");
244
+ }
245
+ }
246
+ }
247
+ writeClassForEnum(record) {
248
+ const { namer, typeSpeller } = this;
249
+ const { recordMap } = typeSpeller;
250
+ const { fields: variants } = record.record;
251
+ const constantVariants = variants.filter((v) => !v.type);
252
+ const wrapperVariants = variants.filter((v) => v.type);
253
+ const className = namer.getClassName(record);
254
+ const qualifiedName = className.qualifiedName;
255
+ this.push(commentify([docToCommentText(record.record.doc), "\nDeeply immutable."]), `sealed class ${className.name} private constructor() {\n`, `/** The kind of variant held by a \`${className.name}\`. */\n`, "enum class Kind {\n", //
256
+ "UNKNOWN,\n");
257
+ for (const variant of constantVariants) {
258
+ this.push(`${variant.name.text}_CONST,\n`);
259
+ }
260
+ for (const variant of wrapperVariants) {
261
+ this.push(convertCase(variant.name.text, "UPPER_UNDERSCORE"), "_WRAPPER,\n");
262
+ }
263
+ this.push("}\n\n", 'class Unknown @kotlin.Deprecated("For internal use", kotlin.ReplaceWith("', qualifiedName, '.UNKNOWN")) internal constructor(\n', `internal val _unrecognized: _UnrecognizedVariant<${qualifiedName}>?,\n`, `) : ${qualifiedName}() {\n`, "override val kind get() = Kind.UNKNOWN;\n\n", "override fun equals(other: kotlin.Any?): kotlin.Boolean {\n", "return other is Unknown;\n", "}\n\n", "override fun hashCode(): kotlin.Int {\n", "return -900601970;\n", "}\n\n", "}\n\n");
264
+ for (const constantVariant of constantVariants) {
265
+ const kindExpr = `Kind.${constantVariant.name.text}_CONST`;
266
+ const constantName = toEnumConstantName(constantVariant);
267
+ this.push(commentify(docToCommentText(constantVariant.doc)), `object ${constantName} : ${qualifiedName}() {\n`, `override val kind get() = ${kindExpr};\n\n`, "init {\n", "_maybeFinalizeSerializer();\n", "}\n", `}\n\n`);
268
+ }
269
+ for (const wrapperVariant of wrapperVariants) {
270
+ const valueType = wrapperVariant.type;
271
+ const wrapperClassName = convertCase(wrapperVariant.name.text, "UpperCamel") + "Wrapper";
272
+ const initializerType = typeSpeller
273
+ .getKotlinType(valueType, "initializer")
274
+ .toString();
275
+ const frozenType = typeSpeller
276
+ .getKotlinType(valueType, "frozen")
277
+ .toString();
278
+ this.pushEol();
279
+ this.push(commentify(docToCommentText(wrapperVariant.doc)));
280
+ if (initializerType === frozenType) {
281
+ this.push(`class ${wrapperClassName}(\n`, `val value: ${initializerType},\n`, `) : ${qualifiedName}() {\n`);
282
+ }
283
+ else {
284
+ this.push(`class ${wrapperClassName} private constructor (\n`, `val value: ${frozenType},\n`, `) : ${qualifiedName}() {\n`, "constructor(\n", `value: ${initializerType},\n`, `): this(${this.toFrozenExpression("value", valueType)}) {}\n\n`);
285
+ }
286
+ const kindExpr = "Kind." +
287
+ convertCase(wrapperVariant.name.text, "UPPER_UNDERSCORE") +
288
+ "_WRAPPER";
289
+ this.push(`override val kind get() = ${kindExpr};\n\n`, "override fun equals(other: kotlin.Any?): kotlin.Boolean {\n", `return other is ${qualifiedName}.${wrapperClassName} && value == other.value;\n`, "}\n\n", "override fun hashCode(): kotlin.Int {\n", "return this.value.hashCode() + ", String(simpleHash(wrapperVariant.name.text) | 0), ";\n", "}\n\n", "}\n\n");
290
+ }
291
+ this.push("abstract val kind: Kind;\n\n", "override fun toString(): kotlin.String {\n", "return build.skir.internal.toStringImpl(\n", "this,\n", `${qualifiedName}._serializerImpl,\n`, ")\n", "}\n\n", "companion object {\n", commentify([
292
+ `Constant indicating an unknown [${className.name}].`,
293
+ `Default value for fields of type [${className.name}].`,
294
+ ]), 'val UNKNOWN = @kotlin.Suppress("DEPRECATION") Unknown(null);\n\n');
295
+ for (const wrapperVariant of wrapperVariants) {
296
+ const type = wrapperVariant.type;
297
+ if (type.kind !== "record") {
298
+ continue;
299
+ }
300
+ const structLocation = typeSpeller.recordMap.get(type.key);
301
+ const struct = structLocation.record;
302
+ if (struct.recordType !== "struct") {
303
+ continue;
304
+ }
305
+ const structClassName = namer.getClassName(structLocation);
306
+ const createFunName = "create" + convertCase(wrapperVariant.name.text, "UpperCamel");
307
+ const wrapperClassName = convertCase(wrapperVariant.name.text, "UpperCamel") + "Wrapper";
308
+ this.push('@kotlin.Suppress("UNUSED_PARAMETER")\n', `fun ${createFunName}(\n`, "_mustNameArguments: _MustNameArguments =\n_MustNameArguments,\n");
309
+ for (const field of struct.fields) {
310
+ const fieldName = namer.structFieldToKotlinName(field);
311
+ const type = typeSpeller.getKotlinType(field.type, "initializer");
312
+ this.push(`${fieldName}: ${type},\n`);
313
+ }
314
+ this.push(`) = ${wrapperClassName}(\n`, `${structClassName.qualifiedName}(\n`);
315
+ for (const field of struct.fields) {
316
+ const fieldName = namer.structFieldToKotlinName(field);
317
+ this.push(`${fieldName} = ${fieldName},\n`);
318
+ }
319
+ this.push(")\n", ");\n\n");
320
+ }
321
+ this.push("private val _serializerImpl =\n", `build.skir.internal.EnumSerializer.create<${qualifiedName}, Unknown>(\n`, `recordId = "${getRecordId(record)}",\n`, `doc = ${toKotlinStringLiteral(record.record.doc.text)},\n`, "getKindOrdinal = { it.kind.ordinal },\n", "kindCount = Kind.values().size,\n", "unknownInstance = UNKNOWN,\n", 'wrapUnrecognized = { @kotlin.Suppress("DEPRECATION") Unknown(it) },\n', "getUnrecognized = { it._unrecognized },\n)", ";\n\n", `/** Serializer for [${className.name}] instances. */\n`, "val serializer = build.skir.internal.makeSerializer(_serializerImpl);\n\n", `/** Describes the [${className.name}] type. Provides runtime introspection capabilities. */\n`, "val typeDescriptor get() = _serializerImpl.typeDescriptor;\n\n", "init {\n");
322
+ for (const constantVariant of constantVariants) {
323
+ this.push(toEnumConstantName(constantVariant), ";\n");
324
+ }
325
+ this.push("_maybeFinalizeSerializer();\n");
326
+ this.push("}\n\n", // init
327
+ `private var _finalizationCounter = 0;\n\n`, "private fun _maybeFinalizeSerializer() {\n", "_finalizationCounter += 1;\n", `if (_finalizationCounter == ${constantVariants.length + 1}) {\n`);
328
+ for (const variant of constantVariants) {
329
+ this.push("_serializerImpl.addConstantVariant(\n", `${variant.number},\n`, `"${variant.name.text}",\n`, `Kind.${variant.name.text}_CONST.ordinal,\n`, `${toKotlinStringLiteral(variant.doc.text)},\n`, `${toEnumConstantName(variant)},\n`, ");\n");
330
+ }
331
+ for (const variant of wrapperVariants) {
332
+ const serializerExpression = typeSpeller.getSerializerExpression(variant.type);
333
+ const wrapperClassName = convertCase(variant.name.text, "UpperCamel") + "Wrapper";
334
+ const kindConstName = convertCase(variant.name.text, "UPPER_UNDERSCORE") + "_WRAPPER";
335
+ this.push("_serializerImpl.addWrapperVariant(\n", `${variant.number},\n`, `"${variant.name.text}",\n`, `Kind.${kindConstName}.ordinal,\n`, `${serializerExpression},\n`, `${toKotlinStringLiteral(variant.doc.text)},\n`, `{ ${wrapperClassName}(it) },\n`, ") { it.value };\n");
336
+ }
337
+ for (const removedNumber of record.record.removedNumbers) {
338
+ this.push(`_serializerImpl.addRemovedNumber(${removedNumber});\n`);
339
+ }
340
+ this.push("_serializerImpl.finalizeEnum();\n", "}\n", "}\n", // maybeFinalizeSerializer
341
+ "}\n\n");
342
+ // Write the classes for the records nested in `record`.
343
+ const nestedRecords = record.record.nestedRecords.map((r) => recordMap.get(r.key));
344
+ this.writeClassesForRecords(nestedRecords);
345
+ this.push("}\n\n");
346
+ }
347
+ writeMethod(method) {
348
+ const { typeSpeller } = this;
349
+ const methodName = method.name.text;
350
+ const requestType = typeSpeller.getKotlinType(method.requestType, "frozen");
351
+ const requestSerializerExpr = typeSpeller.getSerializerExpression(method.requestType);
352
+ const responseType = typeSpeller.getKotlinType(method.responseType, "frozen");
353
+ const responseSerializerExpr = typeSpeller.getSerializerExpression(method.responseType);
354
+ this.push(commentify(docToCommentText(method.doc)), `val ${methodName}: build.skir.service.Method<\n${requestType},\n${responseType},\n> by kotlin.lazy {\n`, "build.skir.service.Method(\n", `"${methodName}",\n`, `${method.number},\n`, requestSerializerExpr + ",\n", responseSerializerExpr + ",\n", toKotlinStringLiteral(method.doc.text) + ",\n", ")\n", "}\n\n");
355
+ }
356
+ writeConstant(constant) {
357
+ const { typeSpeller } = this;
358
+ const name = constant.name.text;
359
+ const type = constant.type;
360
+ const kotlinType = typeSpeller.getKotlinType(type, "frozen");
361
+ const tryGetKotlinConstLiteral = () => {
362
+ if (type.kind !== "primitive") {
363
+ return undefined;
364
+ }
365
+ const { valueAsDenseJson } = constant;
366
+ switch (type.primitive) {
367
+ case "bool":
368
+ return JSON.stringify(!!valueAsDenseJson);
369
+ case "int32":
370
+ case "string":
371
+ return JSON.stringify(valueAsDenseJson);
372
+ case "int64":
373
+ return `${valueAsDenseJson}L`;
374
+ case "uint64":
375
+ return `${valueAsDenseJson}UL`;
376
+ case "float32": {
377
+ if (valueAsDenseJson === "NaN") {
378
+ return "Float.NaN";
379
+ }
380
+ else if (valueAsDenseJson === "Infinity") {
381
+ return "Float.POSITIVE_INFINITY";
382
+ }
383
+ else if (valueAsDenseJson === "-Infinity") {
384
+ return "Float.NEGATIVE_INFINITY";
385
+ }
386
+ else {
387
+ return JSON.stringify(valueAsDenseJson) + "F";
388
+ }
389
+ }
390
+ case "float64": {
391
+ if (valueAsDenseJson === "NaN") {
392
+ return "Double.NaN";
393
+ }
394
+ else if (valueAsDenseJson === "Infinity") {
395
+ return "Double.POSITIVE_INFINITY";
396
+ }
397
+ else if (valueAsDenseJson === "-Infinity") {
398
+ return "Double.NEGATIVE_INFINITY";
399
+ }
400
+ else {
401
+ return JSON.stringify(valueAsDenseJson);
402
+ }
403
+ }
404
+ default:
405
+ return undefined;
406
+ }
407
+ };
408
+ this.push(commentify(docToCommentText(constant.doc)));
409
+ const kotlinConstLiteral = tryGetKotlinConstLiteral();
410
+ if (kotlinConstLiteral !== undefined) {
411
+ this.push(`const val ${name}: ${kotlinType} = ${kotlinConstLiteral};\n\n`);
412
+ }
413
+ else {
414
+ const serializerExpression = typeSpeller.getSerializerExpression(constant.type);
415
+ const jsonStringLiteral = JSON.stringify(JSON.stringify(constant.valueAsDenseJson));
416
+ this.push(`val ${name}: ${kotlinType} by kotlin.lazy {\n`, serializerExpression, `.fromJsonCode(${jsonStringLiteral})\n`, "}\n\n");
417
+ }
418
+ }
419
+ getDefaultExpression(type) {
420
+ switch (type.kind) {
421
+ case "primitive": {
422
+ switch (type.primitive) {
423
+ case "bool":
424
+ return "false";
425
+ case "int32":
426
+ case "int64":
427
+ case "uint64":
428
+ return "0";
429
+ case "float32":
430
+ return "0.0f";
431
+ case "float64":
432
+ return "0.0";
433
+ case "timestamp":
434
+ return "java.time.Instant.EPOCH";
435
+ case "string":
436
+ return '""';
437
+ case "bytes":
438
+ return "okio.ByteString.EMPTY";
439
+ }
440
+ break;
441
+ }
442
+ case "array": {
443
+ const itemType = this.typeSpeller.getKotlinType(type.item, "frozen");
444
+ if (type.key) {
445
+ const { keyType } = type.key;
446
+ let kotlinKeyType = this.typeSpeller.getKotlinType(keyType, "frozen");
447
+ if (keyType.kind === "record") {
448
+ kotlinKeyType += ".Kind";
449
+ }
450
+ return `build.skir.internal.emptyKeyedList<${itemType}, ${kotlinKeyType}>()`;
451
+ }
452
+ else {
453
+ return `build.skir.internal.emptyFrozenList<${itemType}>()`;
454
+ }
455
+ }
456
+ case "optional": {
457
+ return "null";
458
+ }
459
+ case "record": {
460
+ const record = this.typeSpeller.recordMap.get(type.key);
461
+ const kotlinType = this.typeSpeller.getKotlinType(type, "frozen");
462
+ switch (record.record.recordType) {
463
+ case "struct": {
464
+ return `${kotlinType}.partial()`;
465
+ }
466
+ case "enum": {
467
+ return `${kotlinType}.UNKNOWN`;
468
+ }
469
+ }
470
+ break;
471
+ }
472
+ }
473
+ }
474
+ toFrozenExpression(inputExpr, type) {
475
+ const { namer } = this;
476
+ switch (type.kind) {
477
+ case "primitive": {
478
+ return inputExpr;
479
+ }
480
+ case "array": {
481
+ const itemToFrozenExpr = this.toFrozenExpression("it", type.item);
482
+ if (type.key) {
483
+ const path = type.key.path
484
+ .map((f) => namer.structFieldToKotlinName(f.name.text))
485
+ .join(".");
486
+ if (itemToFrozenExpr === "it") {
487
+ return `build.skir.internal.toKeyedList(${inputExpr}, "${path}", { it.${path} })`;
488
+ }
489
+ else {
490
+ return `build.skir.internal.toKeyedList(${inputExpr}, "${path}", { it.${path} }, { ${itemToFrozenExpr} })`;
491
+ }
492
+ }
493
+ else {
494
+ if (itemToFrozenExpr === "it") {
495
+ return `build.skir.internal.toFrozenList(${inputExpr})`;
496
+ }
497
+ else {
498
+ return `build.skir.internal.toFrozenList(${inputExpr}, { ${itemToFrozenExpr} })`;
499
+ }
500
+ }
501
+ }
502
+ case "optional": {
503
+ const otherExpr = this.toFrozenExpression(inputExpr, type.other);
504
+ if (otherExpr === inputExpr) {
505
+ return otherExpr;
506
+ }
507
+ else {
508
+ return `if (${inputExpr} != null) ${otherExpr} else null`;
509
+ }
510
+ }
511
+ case "record": {
512
+ const record = this.typeSpeller.recordMap.get(type.key);
513
+ if (record.record.recordType === "struct") {
514
+ return `${inputExpr}.toFrozen()`;
515
+ }
516
+ else {
517
+ return inputExpr;
518
+ }
519
+ }
520
+ }
521
+ }
522
+ push(...code) {
523
+ this.code += code.join("");
524
+ }
525
+ pushEol() {
526
+ this.code += "\n";
527
+ }
528
+ joinLinesAndFixFormatting() {
529
+ const indentUnit = " ";
530
+ let result = "";
531
+ // The indent at every line is obtained by repeating indentUnit N times,
532
+ // where N is the length of this array.
533
+ const contextStack = [];
534
+ // Returns the last element in `contextStack`.
535
+ const peakTop = () => contextStack.at(-1);
536
+ const getMatchingLeftBracket = (r) => {
537
+ switch (r) {
538
+ case "}":
539
+ return "{";
540
+ case ")":
541
+ return "(";
542
+ case "]":
543
+ return "[";
544
+ case ">":
545
+ return "<";
546
+ }
547
+ };
548
+ for (let line of this.code.split("\n")) {
549
+ line = line.trim();
550
+ if (line.length <= 0) {
551
+ // Don't indent empty lines.
552
+ result += "\n";
553
+ continue;
554
+ }
555
+ const firstChar = line[0];
556
+ switch (firstChar) {
557
+ case "}":
558
+ case ")":
559
+ case "]":
560
+ case ">": {
561
+ const left = getMatchingLeftBracket(firstChar);
562
+ while (contextStack.pop() !== left) {
563
+ if (contextStack.length <= 0) {
564
+ throw Error();
565
+ }
566
+ }
567
+ break;
568
+ }
569
+ case ".": {
570
+ if (peakTop() !== ".") {
571
+ contextStack.push(".");
572
+ }
573
+ break;
574
+ }
575
+ }
576
+ const indent = indentUnit.repeat(contextStack.length) +
577
+ (line.startsWith("*") ? " " : "");
578
+ result += `${indent}${line.trimEnd()}\n`;
579
+ if (line.startsWith("/") || line.startsWith("*")) {
580
+ // A comment.
581
+ continue;
582
+ }
583
+ const lastChar = line.slice(-1);
584
+ switch (lastChar) {
585
+ case "{":
586
+ case "(":
587
+ case "[":
588
+ case "<": {
589
+ // The next line will be indented
590
+ contextStack.push(lastChar);
591
+ break;
592
+ }
593
+ case ":":
594
+ case "=": {
595
+ if (peakTop() !== ":") {
596
+ contextStack.push(":");
597
+ }
598
+ break;
599
+ }
600
+ case ";":
601
+ case ",": {
602
+ if (peakTop() === "." || peakTop() === ":") {
603
+ contextStack.pop();
604
+ }
605
+ }
606
+ }
607
+ }
608
+ return (result
609
+ // Remove spaces enclosed within curly brackets if that's all there is.
610
+ .replace(/\{\s+\}/g, "{}")
611
+ // Remove spaces enclosed within round brackets if that's all there is.
612
+ .replace(/\(\s+\)/g, "()")
613
+ // Remove spaces enclosed within square brackets if that's all there is.
614
+ .replace(/\[\s+\]/g, "[]")
615
+ // Remove empty line following an open curly bracket.
616
+ .replace(/(\{\n *)\n/g, "$1")
617
+ // Remove empty line preceding a closed curly bracket.
618
+ .replace(/\n(\n *\})/g, "$1")
619
+ // Coalesce consecutive empty lines.
620
+ .replace(/\n\n\n+/g, "\n\n")
621
+ .replace(/\n\n$/g, "\n"));
622
+ }
623
+ }
624
+ function getRecordId(struct) {
625
+ const modulePath = struct.modulePath;
626
+ const qualifiedRecordName = struct.recordAncestors
627
+ .map((r) => r.name.text)
628
+ .join(".");
629
+ return `${modulePath}:${qualifiedRecordName}`;
630
+ }
631
+ function toKotlinStringLiteral(input) {
632
+ // Escape special characters for Kotlin string literals
633
+ const escaped = input
634
+ .replace(/\\/g, "\\\\") // Escape backslashes
635
+ .replace(/"/g, '\\"') // Escape double quotes
636
+ .replace(/\n/g, "\\n") // Escape newlines
637
+ .replace(/\r/g, "\\r") // Escape carriage returns
638
+ .replace(/\t/g, "\\t") // Escape tabs
639
+ .replace(/\$/g, "\\$"); // Escape $ to prevent unwanted interpolation
640
+ return `"${escaped}"`;
641
+ }
642
+ function commentify(textOrLines) {
643
+ const text = (typeof textOrLines === "string" ? textOrLines : textOrLines.join("\n"))
644
+ .trim()
645
+ .replace(/\n{3,}/g, "\n\n")
646
+ .replace("*/", "* /");
647
+ if (text.length <= 0) {
648
+ return "";
649
+ }
650
+ const lines = text.split("\n");
651
+ if (lines.length === 1) {
652
+ return `/** ${text} */\n`;
653
+ }
654
+ else {
655
+ return ["/**\n", ...lines.map((line) => ` * ${line}\n`), " */\n"].join("");
656
+ }
657
+ }
658
+ function docToCommentText(doc) {
659
+ return doc.pieces
660
+ .map((p) => {
661
+ switch (p.kind) {
662
+ case "text":
663
+ return p.text;
664
+ case "reference":
665
+ return "`" + p.referenceRange.text.slice(1, -1) + "`";
666
+ }
667
+ })
668
+ .join("");
669
+ }
670
+ export const GENERATOR = new KotlinCodeGenerator();
671
+ //# sourceMappingURL=index.js.map