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 +21 -0
- package/README.md +0 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +611 -0
- package/dist/index.js.map +1 -0
- package/dist/keyed_array_context.d.ts +23 -0
- package/dist/keyed_array_context.d.ts.map +1 -0
- package/dist/keyed_array_context.js +91 -0
- package/dist/keyed_array_context.js.map +1 -0
- package/dist/naming.d.ts +24 -0
- package/dist/naming.d.ts.map +1 -0
- package/dist/naming.js +109 -0
- package/dist/naming.js.map +1 -0
- package/dist/rust_module_spec.d.ts +17 -0
- package/dist/rust_module_spec.d.ts.map +1 -0
- package/dist/rust_module_spec.js +51 -0
- package/dist/rust_module_spec.js.map +1 -0
- package/dist/type_speller.d.ts +16 -0
- package/dist/type_speller.d.ts.map +1 -0
- package/dist/type_speller.js +185 -0
- package/dist/type_speller.js.map +1 -0
- package/package.json +61 -0
- package/src/index.ts +781 -0
- package/src/keyed_array_context.ts +135 -0
- package/src/naming.ts +119 -0
- package/src/rust_module_spec.ts +72 -0
- package/src/type_speller.ts +195 -0
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
|
package/dist/index.d.ts
ADDED
|
@@ -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
|