skir-rust-gen 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Tyler Fibonacci
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
File without changes
@@ -0,0 +1,12 @@
1
+ import { type CodeGenerator } from "skir-internal";
2
+ import { z } from "zod";
3
+ declare const Config: z.ZodObject<{}, z.core.$strict>;
4
+ type Config = z.infer<typeof Config>;
5
+ declare class RustCodeGenerator implements CodeGenerator<Config> {
6
+ readonly id = "skir-rust-gen";
7
+ readonly configType: z.ZodObject<{}, z.core.$strict>;
8
+ generateCode(input: CodeGenerator.Input<Config>): CodeGenerator.Output;
9
+ }
10
+ export declare const GENERATOR: RustCodeGenerator;
11
+ export {};
12
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,aAAa,EASnB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAWxB,QAAA,MAAM,MAAM,iCAAqB,CAAC;AAElC,KAAK,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,MAAM,CAAC,CAAC;AAErC,cAAM,iBAAkB,YAAW,aAAa,CAAC,MAAM,CAAC;IACtD,QAAQ,CAAC,EAAE,mBAAmB;IAC9B,QAAQ,CAAC,UAAU,kCAAU;IAE7B,YAAY,CAAC,KAAK,EAAE,aAAa,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,aAAa,CAAC,MAAM;CAevE;AA+tBD,eAAO,MAAM,SAAS,mBAA0B,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,611 @@
1
+ import { convertCase, } from "skir-internal";
2
+ import { z } from "zod";
3
+ import { KeyedArrayContext } from "./keyed_array_context.js";
4
+ import { getTypeName, isUpperCasedKeyword, Namer, toStructFieldName as toRustFieldName, } from "./naming.js";
5
+ import { collectRustModuleSpecs } from "./rust_module_spec.js";
6
+ import { skirDefaultIsRustDefault, TypeSpeller } from "./type_speller.js";
7
+ const Config = z.strictObject({});
8
+ class RustCodeGenerator {
9
+ constructor() {
10
+ this.id = "skir-rust-gen";
11
+ this.configType = Config;
12
+ }
13
+ generateCode(input) {
14
+ const { recordMap, config } = input;
15
+ const keyedArrayContext = new KeyedArrayContext(input.modules);
16
+ const rustModuleSpecs = collectRustModuleSpecs(input.modules);
17
+ const outputFiles = rustModuleSpecs.map((moduleSpec) => ({
18
+ path: moduleSpec.path,
19
+ code: new RustSourceFileGenerator(moduleSpec, recordMap, keyedArrayContext, config).generate(),
20
+ }));
21
+ return { files: outputFiles };
22
+ }
23
+ }
24
+ // Generates the code for one Rust file.
25
+ class RustSourceFileGenerator {
26
+ constructor(moduleSpec, recordMap, keyedArrayContext, config) {
27
+ this.moduleSpec = moduleSpec;
28
+ this.keyedArrayContext = keyedArrayContext;
29
+ this.config = config;
30
+ this.code = "";
31
+ this.namer = new Namer(moduleSpec.skirModule);
32
+ this.typeSpeller = new TypeSpeller(recordMap, this.namer);
33
+ }
34
+ generate() {
35
+ // const packageName = skirModulePathToSkiroutPath(this.inModule.path);
36
+ // http://patorjk.com/software/taag/#f=Doom&t=Do%20not%20edit
37
+ this.push(`#![allow(nonstandard_style)]
38
+
39
+ // ______ _ _ _ _
40
+ // | _ \\ | | | |(_)| |
41
+ // | | | | ___ _ __ ___ | |_ ___ __| | _ | |_
42
+ // | | | | / _ \\ | '_ \\ / _ \\ | __| / _ \\ / _\` || || __|
43
+ // | |/ / | (_) | | | | || (_) || |_ | __/| (_| || || |_
44
+ // |___/ \\___/ |_| |_| \\___/ \\__| \\___| \\__,_||_| \\__|
45
+ //
46
+ // Generated by skir-rust-gen
47
+ // Home: https://github.com/gepheum/skir-rust-gen
48
+ //
49
+ // To install the Skir client library, run:
50
+ // cargo add skir-rust-client
51
+ `);
52
+ for (const childModuleName of this.moduleSpec.childModuleNames) {
53
+ this.push(`pub mod ${childModuleName};\n`);
54
+ }
55
+ this.push("\n");
56
+ const skirModule = this.moduleSpec.skirModule;
57
+ if (skirModule) {
58
+ const { constants, methods, records } = skirModule;
59
+ if (records.length) {
60
+ for (const record of records) {
61
+ const { recordType } = record.record;
62
+ if (recordType === "struct") {
63
+ this.writeStruct(record);
64
+ }
65
+ else {
66
+ this.writeEnum(record);
67
+ }
68
+ }
69
+ this.writeInitializeModuleSerializersFn(records);
70
+ }
71
+ if (methods.length) {
72
+ this.pushSeparator("Methods");
73
+ for (const method of methods) {
74
+ this.writeMethod(method);
75
+ }
76
+ }
77
+ if (constants.length) {
78
+ this.pushSeparator("Constants");
79
+ for (const constant of constants) {
80
+ this.writeConstant(constant);
81
+ }
82
+ }
83
+ }
84
+ return this.joinLinesAndFixFormatting();
85
+ }
86
+ writeStruct(struct) {
87
+ const { namer, typeSpeller } = this;
88
+ this.pushSeparator("struct ".concat(struct.recordAncestors.map((r) => r.name.text).join(".")));
89
+ const typeName = getTypeName(struct);
90
+ const allFieldsUseRustDefault = struct.record.fields.every((f) => f.isRecursive === "hard" || skirDefaultIsRustDefault(f.type));
91
+ const deriveList = allFieldsUseRustDefault
92
+ ? `${namer.clone}, ${namer.debug}, ${namer.partialEq}, ${namer.default}`
93
+ : `${namer.clone}, ${namer.debug}, ${namer.partialEq}`;
94
+ this.push(commentify(docToCommentText(struct.record.doc)));
95
+ this.push(`#[derive(${deriveList})]\n`);
96
+ this.push(`pub struct ${typeName} {\n`);
97
+ for (const field of struct.record.fields) {
98
+ const fieldType = typeSpeller.getRustType(field.type);
99
+ const fieldName = toRustFieldName(field.name.text);
100
+ if (field.isRecursive === "hard") {
101
+ const recFieldName = `_${field.name.text}_rec`;
102
+ const boxedType = `${namer.option}<${namer.box}<${fieldType}>>`;
103
+ this.push(commentify([
104
+ docToCommentText(field.doc),
105
+ "Recursive field. Noxed and optional to avoid infinite size.",
106
+ "None is equivalent to the default value.",
107
+ `Use \`${fieldName}()\` to read this field without having to handle the Option,`,
108
+ "but be careful not to call it from a recursive function as it may cause",
109
+ "infinite recursion.",
110
+ ]));
111
+ this.push(`pub ${recFieldName}: ${boxedType},\n`);
112
+ }
113
+ else {
114
+ this.push(commentify(docToCommentText(field.doc)));
115
+ this.push(`pub ${fieldName}: ${fieldType},\n`);
116
+ }
117
+ }
118
+ this.push(commentify("Set this to None when you're creating a struct."));
119
+ this.push(`pub _unrecognized: ${namer.option}<crate::skir_client::UnrecognizedFields<${typeName}>>,\n`);
120
+ this.push("}\n\n");
121
+ // impl block: default_ref() + getters for hard-recursive fields
122
+ const hardRecursiveFields = struct.record.fields.filter((f) => f.isRecursive === "hard");
123
+ this.push(`impl ${typeName} {\n`);
124
+ // default_ref()
125
+ this.push(`pub fn default_ref() -> &'static ${typeName} {\n`);
126
+ this.push(`static D: std::sync::LazyLock<${typeName}> = std::sync::LazyLock::new(${typeName}::default);\n`);
127
+ this.push("&D\n");
128
+ this.push("}\n");
129
+ // Getters for hard-recursive fields
130
+ for (const field of hardRecursiveFields) {
131
+ const getterName = toRustFieldName(field.name.text);
132
+ const fieldType = typeSpeller.getRustType(field.type);
133
+ this.push(commentify(docToCommentText(field.doc)));
134
+ this.push(`pub fn ${getterName}(&self) -> &${fieldType} {\n`);
135
+ this.push(`match &self._${field.name.text}_rec {\n`);
136
+ this.push(`Some(boxed) => boxed.as_ref(),\n`);
137
+ this.push(`None => ${fieldType}::default_ref(),\n`);
138
+ this.push("}\n");
139
+ this.push("}\n");
140
+ }
141
+ this.push("}\n\n");
142
+ // Manual Default impl — only needed when #[derive(Default)] can't be used.
143
+ if (!allFieldsUseRustDefault) {
144
+ this.push(`impl ${namer.default} for ${typeName} {\n`);
145
+ this.push(`fn default() -> Self {\n`);
146
+ this.push(`${typeName} {\n`);
147
+ for (const field of struct.record.fields) {
148
+ if (field.isRecursive === "hard") {
149
+ this.push(`_${field.name.text}_rec: None,\n`);
150
+ }
151
+ else {
152
+ const fieldName = toRustFieldName(field.name.text);
153
+ const defaultExpr = typeSpeller.getDefaultExpr(field.type);
154
+ this.push(`${fieldName}: ${defaultExpr},\n`);
155
+ }
156
+ }
157
+ this.push(`_unrecognized: None,\n`);
158
+ this.push("}\n");
159
+ this.push("}\n");
160
+ this.push("}\n\n");
161
+ }
162
+ // Write a KeyedVecSpec impl for each keyed array that has this struct as item type.
163
+ for (const keySpec of this.keyedArrayContext.getKeySpecsForItemStruct(struct.record, typeSpeller)) {
164
+ this.push(`pub struct ${keySpec.rustSpecName};\n\n`);
165
+ this.push(`impl crate::skir_client::KeyedVecSpec for ${keySpec.rustSpecName} {\n`);
166
+ this.push(`type Item = ${typeName};\n`);
167
+ this.push(`type StorageKey = ${keySpec.rustKeyType};\n`);
168
+ this.push(`type Lookup = crate::skir_client::internal::${keySpec.lookupImpl};\n`);
169
+ this.push(`fn get_key(item: &${typeName}) -> ${keySpec.rustKeyType} {\n`);
170
+ this.push(`${keySpec.rustKeyExpr}\n`);
171
+ this.push("}\n");
172
+ this.push(`fn key_extractor() -> &'static str {\n`);
173
+ this.push(`"${keySpec.keyExtractor}"\n`);
174
+ this.push("}\n");
175
+ this.push(`fn default_item() -> &'static ${typeName} {\n`);
176
+ this.push(`${typeName}::default_ref()\n`);
177
+ this.push("}\n");
178
+ this.push("}\n\n");
179
+ }
180
+ const structModulePath = this.moduleSpec.skirModule.path;
181
+ const structQualifiedName = struct.recordAncestors
182
+ .map((r) => r.name.text)
183
+ .join(".");
184
+ this.push(`impl ${typeName} {\n`);
185
+ this.push(`fn _adapter() -> &'static crate::skir_client::internal::StructAdapter<${typeName}> {\n`);
186
+ this.push(`static ADAPTER: std::sync::LazyLock<crate::skir_client::internal::StructAdapter<${typeName}>> =\n`);
187
+ this.push(`std::sync::LazyLock::new(|| {\n`);
188
+ this.push(`crate::skir_client::internal::StructAdapter::new(\n`);
189
+ this.push(`"${structModulePath}",\n`);
190
+ this.push(`"${structQualifiedName}",\n`);
191
+ this.push(`${toRustStringLiteral(docToCommentText(struct.record.doc))},\n`);
192
+ this.push(`|x: &${typeName}| &x._unrecognized,\n`);
193
+ this.push(`|x: &mut ${typeName}, u| x._unrecognized = u,\n`);
194
+ this.push(`)\n`);
195
+ this.push(`});\n`);
196
+ this.push(`&*ADAPTER\n`);
197
+ this.push("}\n");
198
+ this.push(`pub fn serializer() -> crate::skir_client::Serializer<${typeName}> {\n`);
199
+ this.push(`initialize_module_serializers();\n`);
200
+ this.push(`crate::skir_client::internal::struct_serializer_from_static(${typeName}::_adapter())\n`);
201
+ this.push("}\n");
202
+ this.push("}\n\n");
203
+ }
204
+ writeEnum(record) {
205
+ const { namer, typeSpeller } = this;
206
+ this.pushSeparator("enum ".concat(record.recordAncestors.map((r) => r.name.text).join(".")));
207
+ const typeName = getTypeName(record);
208
+ this.push(commentify(docToCommentText(record.record.doc)));
209
+ this.push(`#[derive(${namer.debug}, ${namer.clone}, ${namer.partialEq})]\n`);
210
+ this.push(`pub enum ${typeName} {\n`);
211
+ this.push(`Unknown(${namer.option}<crate::skir_client::UnrecognizedVariant<${typeName}>>),\n`);
212
+ const variantNamesNeedSuffix = doVariantNamesNeedSuffix(record.record.fields);
213
+ for (const variant of record.record.fields) {
214
+ const variantName = convertCase(variant.name.text, "UpperCamel").concat(variantNamesNeedSuffix ? (variant.type ? "Wrapper" : "Const") : "");
215
+ this.push(commentify(docToCommentText(variant.doc)));
216
+ if (variant.type) {
217
+ const variantType = variant.type;
218
+ let valueRustType = typeSpeller.getRustType(variantType);
219
+ if (doesWrapperVariantNeedBoxing(variantType)) {
220
+ valueRustType = `${namer.box}<${valueRustType}>`;
221
+ }
222
+ this.push(`${variantName}(${valueRustType}),\n`);
223
+ }
224
+ else {
225
+ this.push(`${variantName},\n`);
226
+ }
227
+ }
228
+ this.push("}\n\n");
229
+ this.push(`impl ${namer.default} for ${typeName} {\n`);
230
+ this.push(`fn default() -> Self {\n`);
231
+ this.push(`${typeName}::Unknown(None)\n`);
232
+ this.push("}\n");
233
+ this.push("}\n\n");
234
+ if (this.keyedArrayContext.isEnumUsedAsKey(record.record)) {
235
+ // Write the _kind enum.
236
+ this.push(`#[derive(${namer.clone}, ${namer.copy}, ${namer.debug}, ${namer.eq}, ${namer.hash}, ${namer.partialEq})]\n`);
237
+ this.push(`pub enum ${typeName}_kind {\n`);
238
+ this.push("Unknown,\n");
239
+ for (const variant of record.record.fields) {
240
+ const variantName = convertCase(variant.name.text, "UpperCamel").concat(variantNamesNeedSuffix ? (variant.type ? "Wrapper" : "Const") : "");
241
+ this.push(`${variantName},\n`);
242
+ }
243
+ this.push("}\n\n");
244
+ // Write the kind() getter on the main enum.
245
+ this.push(`impl ${typeName} {\n`);
246
+ this.push(`pub fn kind(&self) -> ${typeName}_kind {\n`);
247
+ this.push(`match self {\n`);
248
+ this.push(`${typeName}::Unknown(_) => ${typeName}_kind::Unknown,\n`);
249
+ for (const variant of record.record.fields) {
250
+ const variantName = convertCase(variant.name.text, "UpperCamel").concat(variantNamesNeedSuffix ? (variant.type ? "Wrapper" : "Const") : "");
251
+ if (variant.type) {
252
+ this.push(`${typeName}::${variantName}(_) => ${typeName}_kind::${variantName},\n`);
253
+ }
254
+ else {
255
+ this.push(`${typeName}::${variantName} => ${typeName}_kind::${variantName},\n`);
256
+ }
257
+ }
258
+ this.push("}\n");
259
+ this.push("}\n");
260
+ this.push("}\n\n");
261
+ }
262
+ const enumModulePath = this.moduleSpec.skirModule.path;
263
+ const enumQualifiedName = record.recordAncestors
264
+ .map((r) => r.name.text)
265
+ .join(".");
266
+ this.push(`impl ${typeName} {\n`);
267
+ this.push(`fn _adapter() -> &'static crate::skir_client::internal::EnumAdapter<${typeName}> {\n`);
268
+ this.push(`static ADAPTER: std::sync::LazyLock<crate::skir_client::internal::EnumAdapter<${typeName}>> =\n`);
269
+ this.push(`std::sync::LazyLock::new(|| {\n`);
270
+ this.push(`crate::skir_client::internal::EnumAdapter::new(\n`);
271
+ this.push(`|x: &${typeName}| match x {\n`);
272
+ this.push(`${typeName}::Unknown(_) => 0,\n`);
273
+ let kindOrdinal = 1;
274
+ for (const variant of record.record.fields) {
275
+ const variantName = convertCase(variant.name.text, "UpperCamel").concat(variantNamesNeedSuffix ? (variant.type ? "Wrapper" : "Const") : "");
276
+ if (variant.type) {
277
+ this.push(`${typeName}::${variantName}(_) => ${kindOrdinal},\n`);
278
+ }
279
+ else {
280
+ this.push(`${typeName}::${variantName} => ${kindOrdinal},\n`);
281
+ }
282
+ kindOrdinal++;
283
+ }
284
+ this.push(`},\n`);
285
+ this.push(`|u| ${typeName}::Unknown(Some(u)),\n`);
286
+ this.push(`|x: &${typeName}| match x { ${typeName}::Unknown(Some(u)) => Some(u.as_ref()), _ => None },\n`);
287
+ this.push(`"${enumModulePath}",\n`);
288
+ this.push(`"${enumQualifiedName}",\n`);
289
+ this.push(`${toRustStringLiteral(docToCommentText(record.record.doc))},\n`);
290
+ this.push(`)\n`);
291
+ this.push(`});\n`);
292
+ this.push(`&*ADAPTER\n`);
293
+ this.push("}\n");
294
+ this.push(`pub fn serializer() -> crate::skir_client::Serializer<${typeName}> {\n`);
295
+ this.push(`initialize_module_serializers();\n`);
296
+ this.push(`crate::skir_client::internal::enum_serializer_from_static(${typeName}::_adapter())\n`);
297
+ this.push("}\n");
298
+ this.push("}\n\n");
299
+ }
300
+ writeInitializeModuleSerializersFn(records) {
301
+ const { typeSpeller } = this;
302
+ this.pushSeparator("initialize_module_serializers()");
303
+ this.push("fn initialize_module_serializers() {\n");
304
+ this.push("static INIT: std::sync::LazyLock<()> =\n");
305
+ this.push("std::sync::LazyLock::new(|| {\n");
306
+ for (const record of records) {
307
+ const typeName = getTypeName(record);
308
+ if (record.record.recordType === "struct") {
309
+ this.push("unsafe {\n");
310
+ this.push(`let a: *mut crate::skir_client::internal::StructAdapter<${typeName}> = ${typeName}::_adapter() as *const _ as *mut _;\n`);
311
+ for (const removedNumber of record.record.removedNumbers) {
312
+ this.push(`(*a).add_removed_number(${removedNumber});\n`);
313
+ }
314
+ for (const field of record.record.fields) {
315
+ const fieldName = toRustFieldName(field.name.text);
316
+ let serializerExpr = typeSpeller.getSerializerExpression(field.type, "init");
317
+ if (field.isRecursive === "hard") {
318
+ serializerExpr = `crate::skir_client::internal::recursive_serializer(${serializerExpr})`;
319
+ }
320
+ const getter = field.isRecursive === "hard"
321
+ ? `|x: &${typeName}| &x._${field.name.text}_rec`
322
+ : `|x: &${typeName}| &x.${fieldName}`;
323
+ const setter = field.isRecursive === "hard"
324
+ ? `|x: &mut ${typeName}, v| x._${field.name.text}_rec = v`
325
+ : `|x: &mut ${typeName}, v| x.${fieldName} = v`;
326
+ this.push(`(*a).add_field("${field.name.text}", ${field.number}, ${serializerExpr}, ${toRustStringLiteral(docToCommentText(field.doc))}, ${getter}, ${setter});\n`);
327
+ }
328
+ this.push("(*a).finalize();\n");
329
+ this.push("}\n");
330
+ }
331
+ else {
332
+ const variantNamesNeedSuffix = doVariantNamesNeedSuffix(record.record.fields);
333
+ this.push("unsafe {\n");
334
+ this.push(`let a: *mut crate::skir_client::internal::EnumAdapter<${typeName}> = ${typeName}::_adapter() as *const _ as *mut _;\n`);
335
+ for (const removedNumber of record.record.removedNumbers) {
336
+ this.push(`(*a).add_removed_number(${removedNumber});\n`);
337
+ }
338
+ let kindOrdinal = 1;
339
+ for (const variant of record.record.fields) {
340
+ const variantName = convertCase(variant.name.text, "UpperCamel").concat(variantNamesNeedSuffix ? (variant.type ? "Wrapper" : "Const") : "");
341
+ if (variant.type) {
342
+ const serializerExpr = typeSpeller.getSerializerExpression(variant.type, "init");
343
+ let wrapFn;
344
+ let getValueFn;
345
+ if (doesWrapperVariantNeedBoxing(variant.type)) {
346
+ wrapFn = `|v| ${typeName}::${variantName}(Box::new(v))`;
347
+ getValueFn = `|x| match x { ${typeName}::${variantName}(b) => b.as_ref(), _ => unreachable!() }`;
348
+ }
349
+ else {
350
+ wrapFn = `|v| ${typeName}::${variantName}(v)`;
351
+ getValueFn = `|x| match x { ${typeName}::${variantName}(v) => v, _ => unreachable!() }`;
352
+ }
353
+ this.push(`(*a).add_wrapper_variant("${variant.name.text}", ${variant.number}, ${kindOrdinal}, ${serializerExpr}, ${toRustStringLiteral(docToCommentText(variant.doc))}, ${wrapFn}, ${getValueFn});\n`);
354
+ }
355
+ else {
356
+ this.push(`(*a).add_constant_variant("${variant.name.text}", ${variant.number}, ${kindOrdinal}, ${toRustStringLiteral(docToCommentText(variant.doc))}, ${typeName}::${variantName});\n`);
357
+ }
358
+ kindOrdinal++;
359
+ }
360
+ this.push("(*a).finalize();\n");
361
+ this.push("}\n");
362
+ }
363
+ }
364
+ this.push("});\n");
365
+ this.push("let _ = *INIT;\n");
366
+ this.push("}\n\n");
367
+ }
368
+ writeMethod(method) {
369
+ const { typeSpeller } = this;
370
+ const rustName = convertCase(method.name.text, "lower_underscore").concat("_method");
371
+ const requestRustType = typeSpeller.getRustType(method.requestType);
372
+ const responseRustType = typeSpeller.getRustType(method.responseType);
373
+ const requestSerializerExpr = typeSpeller.getSerializerExpression(method.requestType, null);
374
+ const responseSerializerExpr = typeSpeller.getSerializerExpression(method.responseType, null);
375
+ const nameStr = toRustStringLiteral(method.name.text);
376
+ const docStr = toRustStringLiteral(docToCommentText(method.doc));
377
+ this.push(commentify(docToCommentText(method.doc)));
378
+ this.push(`pub fn ${rustName}() -> &'static crate::skir_client::Method<${requestRustType}, ${responseRustType}> {\n`);
379
+ this.push(`static METHOD: std::sync::LazyLock<crate::skir_client::Method<${requestRustType}, ${responseRustType}>> = std::sync::LazyLock::new(|| {\n`);
380
+ this.push(`crate::skir_client::Method {\n`);
381
+ this.push(`name: ${nameStr}.to_string(),\n`);
382
+ this.push(`number: ${method.number}_i64,\n`);
383
+ this.push(`request_serializer: ${requestSerializerExpr},\n`);
384
+ this.push(`response_serializer: ${responseSerializerExpr},\n`);
385
+ this.push(`doc: ${docStr}.to_string(),\n`);
386
+ this.push("}\n");
387
+ this.push("});\n");
388
+ this.push("&*METHOD\n");
389
+ this.push("}\n\n");
390
+ }
391
+ writeConstant(constant) {
392
+ const { typeSpeller } = this;
393
+ const rustName = convertCase(constant.name.text, "lower_underscore").concat("_const");
394
+ const type = constant.type;
395
+ this.push(commentify(docToCommentText(constant.doc)));
396
+ const rustLiteral = tryGetRustLiteral(constant);
397
+ if (rustLiteral !== null) {
398
+ // This type can be represented as a real Rust const.
399
+ // String primitives must use &'static str because String is not
400
+ // const-eligible in Rust.
401
+ const constType = type.kind === "primitive" && type.primitive === "string"
402
+ ? "&'static str"
403
+ : typeSpeller.getRustType(type);
404
+ this.push(`pub const ${rustName}: ${constType} = ${rustLiteral};\n\n`);
405
+ }
406
+ else {
407
+ // Use LazyLock for lazy initialization from JSON.
408
+ const rustType = typeSpeller.getRustType(type);
409
+ const serializerExpr = typeSpeller.getSerializerExpression(type, null);
410
+ const jsonLiteral = toRustStringLiteral(JSON.stringify(constant.valueAsDenseJson));
411
+ this.push(`pub fn ${rustName}() -> &'static ${rustType} {\n`);
412
+ this.push(`static VALUE: std::sync::LazyLock<${rustType}> = std::sync::LazyLock::new(|| {\n`);
413
+ this.push(`${serializerExpr}.from_json(${jsonLiteral}, crate::skir_client::UnrecognizedValues::Drop).unwrap()\n`);
414
+ this.push("});\n");
415
+ this.push("&*VALUE\n");
416
+ this.push("}\n\n");
417
+ }
418
+ }
419
+ pushSeparator(header) {
420
+ this.push(`// ${"=".repeat(78)}\n`);
421
+ this.push(`// ${header}\n`);
422
+ this.push(`// ${"=".repeat(78)}\n\n`);
423
+ }
424
+ push(...code) {
425
+ this.code += code.join("");
426
+ }
427
+ joinLinesAndFixFormatting() {
428
+ const indentUnit = " ";
429
+ let result = "";
430
+ // The indent at every line is obtained by repeating indentUnit N times,
431
+ // where N is the length of this array.
432
+ const contextStack = [];
433
+ // Returns the last element in `contextStack`.
434
+ const peakTop = () => contextStack.at(-1);
435
+ const getMatchingLeftBracket = (r) => {
436
+ switch (r) {
437
+ case "}":
438
+ return "{";
439
+ case ")":
440
+ return "(";
441
+ case "]":
442
+ return "[";
443
+ case ">":
444
+ return "<";
445
+ }
446
+ };
447
+ for (let line of this.code.split("\n")) {
448
+ line = line.trim();
449
+ if (line.length <= 0) {
450
+ // Don't indent empty lines.
451
+ result += "\n";
452
+ continue;
453
+ }
454
+ const firstChar = line[0];
455
+ switch (firstChar) {
456
+ case "}":
457
+ case ")":
458
+ case "]":
459
+ case ">": {
460
+ const left = getMatchingLeftBracket(firstChar);
461
+ while (contextStack.pop() !== left) {
462
+ if (contextStack.length <= 0) {
463
+ throw Error();
464
+ }
465
+ }
466
+ break;
467
+ }
468
+ case ".": {
469
+ if (peakTop() !== ".") {
470
+ contextStack.push(".");
471
+ }
472
+ break;
473
+ }
474
+ }
475
+ const indent = indentUnit.repeat(contextStack.length) +
476
+ (line.startsWith("*") ? " " : "");
477
+ result += `${indent}${line.trimEnd()}\n`;
478
+ if (line.startsWith("/") || line.startsWith("*")) {
479
+ // A comment.
480
+ continue;
481
+ }
482
+ const lastChar = line.slice(-1);
483
+ switch (lastChar) {
484
+ case "{":
485
+ case "(":
486
+ case "[":
487
+ case "<": {
488
+ // The next line will be indented
489
+ contextStack.push(lastChar);
490
+ break;
491
+ }
492
+ case ":":
493
+ case "=": {
494
+ if (peakTop() !== ":") {
495
+ contextStack.push(":");
496
+ }
497
+ break;
498
+ }
499
+ case ";":
500
+ case ",": {
501
+ if (peakTop() === "." || peakTop() === ":") {
502
+ contextStack.pop();
503
+ }
504
+ }
505
+ }
506
+ }
507
+ return (result
508
+ // Remove spaces enclosed within curly brackets if that's all there is.
509
+ .replace(/\{\s+\}/g, "{}")
510
+ // Remove spaces enclosed within round brackets if that's all there is.
511
+ .replace(/\(\s+\)/g, "()")
512
+ // Remove spaces enclosed within square brackets if that's all there is.
513
+ .replace(/\[\s+\]/g, "[]")
514
+ // Remove empty line following an open curly bracket.
515
+ .replace(/(\{\n *)\n/g, "$1")
516
+ // Remove empty line preceding a closed curly bracket.
517
+ .replace(/\n(\n *\})/g, "$1")
518
+ // Coalesce consecutive empty lines.
519
+ .replace(/\n\n\n+/g, "\n\n")
520
+ .replace(/\n\n$/g, "\n"));
521
+ }
522
+ }
523
+ function doVariantNamesNeedSuffix(variants) {
524
+ const seenNames = new Set();
525
+ for (const variant of variants) {
526
+ const name = convertCase(variant.name.text, "UpperCamel");
527
+ if (isUpperCasedKeyword(name) || seenNames.has(name)) {
528
+ return true;
529
+ }
530
+ seenNames.add(name);
531
+ }
532
+ return false;
533
+ }
534
+ function doesWrapperVariantNeedBoxing(type) {
535
+ switch (type.kind) {
536
+ case "array":
537
+ return false;
538
+ case "optional":
539
+ return doesWrapperVariantNeedBoxing(type.other);
540
+ case "primitive":
541
+ return false;
542
+ case "record":
543
+ return true;
544
+ }
545
+ }
546
+ function toRustStringLiteral(input) {
547
+ const escaped = input
548
+ .replace(/\\/g, "\\\\") // Escape backslashes
549
+ .replace(/"/g, '\\"') // Escape double quotes
550
+ .replace(/\n/g, "\\n") // Escape newlines
551
+ .replace(/\r/g, "\\r") // Escape carriage returns
552
+ .replace(/\t/g, "\\t"); // Escape tabs
553
+ return `"${escaped}"`;
554
+ }
555
+ function tryGetRustLiteral(constant) {
556
+ const type = constant.type;
557
+ if (type.kind !== "primitive") {
558
+ return null;
559
+ }
560
+ const valueAsDenseJson = constant.valueAsDenseJson;
561
+ switch (type.primitive) {
562
+ case "bool":
563
+ return valueAsDenseJson ? "true" : "false";
564
+ case "int32":
565
+ case "int64":
566
+ case "hash64":
567
+ case "float32":
568
+ case "float64": {
569
+ const maybeQuoted = valueAsDenseJson.toString();
570
+ if (maybeQuoted === "Infinity" ||
571
+ maybeQuoted === "-Infinity" ||
572
+ maybeQuoted === "NaN") {
573
+ return null;
574
+ }
575
+ else {
576
+ return maybeQuoted.startsWith('"') && maybeQuoted.endsWith('"')
577
+ ? maybeQuoted.slice(1, -1)
578
+ : maybeQuoted;
579
+ }
580
+ }
581
+ case "string":
582
+ return toRustStringLiteral(valueAsDenseJson);
583
+ }
584
+ return null;
585
+ }
586
+ function commentify(textOrLines) {
587
+ const text = (typeof textOrLines === "string" ? textOrLines : textOrLines.join("\n"))
588
+ .trim()
589
+ .replace(/\n{3,}/g, "\n\n");
590
+ if (text.length <= 0) {
591
+ return "";
592
+ }
593
+ return text
594
+ .split("\n")
595
+ .map((line) => (line.length > 0 ? `/// ${line}\n` : `///\n`))
596
+ .join("");
597
+ }
598
+ function docToCommentText(doc) {
599
+ return doc.pieces
600
+ .map((p) => {
601
+ switch (p.kind) {
602
+ case "text":
603
+ return p.text;
604
+ case "reference":
605
+ return "`" + p.referenceRange.text.slice(1, -1) + "`";
606
+ }
607
+ })
608
+ .join("");
609
+ }
610
+ export const GENERATOR = new RustCodeGenerator();
611
+ //# sourceMappingURL=index.js.map