trans_gen_kotlin/
lib.rs

1use std::collections::HashMap;
2use std::fmt::Write;
3use trans_gen_core::trans_schema::*;
4use trans_gen_core::Writer;
5
6fn conv(name: &str) -> String {
7    name.replace("Bool", "Boolean")
8        .replace("Int32", "Int")
9        .replace("Int64", "Long")
10        .replace("Float32", "Float")
11        .replace("Float64", "Double")
12}
13
14pub struct Generator {
15    files: HashMap<String, String>,
16}
17
18fn type_name(schema: &Schema) -> String {
19    match schema {
20        Schema::Bool => "Boolean".to_owned(),
21        Schema::Int32 => "Int".to_owned(),
22        Schema::Int64 => "Long".to_owned(),
23        Schema::Float32 => "Float".to_owned(),
24        Schema::Float64 => "Double".to_owned(),
25        Schema::String => "String".to_owned(),
26        Schema::Struct(Struct { name, .. })
27        | Schema::OneOf {
28            base_name: name, ..
29        }
30        | Schema::Enum {
31            base_name: name, ..
32        } => format!("model.{}", name.camel_case(conv)),
33        Schema::Option(inner) => format!("{}?", type_name(inner)),
34        Schema::Vec(inner) => format!("Array<{}>", type_name(inner)),
35        Schema::Map(key, value) => format!("MutableMap<{}, {}>", type_name(key), type_name(value)),
36    }
37}
38
39fn default_value(schema: &Schema) -> String {
40    match schema {
41        Schema::Bool => "false".to_owned(),
42        Schema::Int32 => "0".to_owned(),
43        Schema::Int64 => "0L".to_owned(),
44        Schema::Float32 => "0.0f".to_owned(),
45        Schema::Float64 => "0.0".to_owned(),
46        Schema::Option(_) => "null".to_owned(),
47        Schema::String
48        | Schema::Struct(_)
49        | Schema::OneOf { .. }
50        | Schema::Enum { .. }
51        | Schema::Vec(_)
52        | Schema::Map(_, _) => unreachable!(),
53    }
54}
55
56fn can_lateinit(schema: &Schema) -> bool {
57    match schema {
58        Schema::Bool
59        | Schema::Int32
60        | Schema::Int64
61        | Schema::Float32
62        | Schema::Float64
63        | Schema::Option(_) => false,
64        Schema::String
65        | Schema::Struct(_)
66        | Schema::OneOf { .. }
67        | Schema::Enum { .. }
68        | Schema::Vec(_)
69        | Schema::Map(_, _) => true,
70    }
71}
72
73fn index_var_name(index_var: &mut usize) -> String {
74    let result = "ijk".chars().nth(*index_var).unwrap();
75    *index_var += 1;
76    result.to_string()
77}
78
79fn var_name(name: &str) -> &str {
80    match name.rfind('.') {
81        Some(index) => &name[(index + 1)..],
82        None => name,
83    }
84}
85
86fn getter_prefix(schema: &Schema) -> &'static str {
87    match schema {
88        Schema::Bool => "is",
89        _ => "get",
90    }
91}
92
93fn write_struct(
94    writer: &mut Writer,
95    struc: &Struct,
96    base: Option<(&Name, usize)>,
97) -> std::fmt::Result {
98    // Class
99    if let Some((base_name, _)) = base {
100        writeln!(
101            writer,
102            "class {} : {} {{",
103            struc.name.camel_case(conv),
104            base_name.camel_case(conv)
105        )?;
106    } else {
107        writeln!(writer, "class {} {{", struc.name.camel_case(conv))?;
108    }
109    writer.inc_ident();
110
111    // Fields
112    for field in &struc.fields {
113        writeln!(
114            writer,
115            "{}var {}: {}{}",
116            if can_lateinit(&field.schema) {
117                "lateinit "
118            } else {
119                ""
120            },
121            field.name.mixed_case(conv),
122            type_name(&field.schema),
123            if can_lateinit(&field.schema) {
124                String::new()
125            } else {
126                format!(" = {}", default_value(&field.schema))
127            }
128        )?;
129    }
130
131    // Constructor
132    writeln!(writer, "constructor() {{}}")?;
133    if !struc.fields.is_empty() {
134        write!(writer, "constructor(")?;
135        for (index, field) in struc.fields.iter().enumerate() {
136            if index > 0 {
137                write!(writer, ", ")?;
138            }
139            write!(
140                writer,
141                "{}: {}",
142                field.name.mixed_case(conv),
143                type_name(&field.schema),
144            )?;
145        }
146        writeln!(writer, ") {{")?;
147        for field in &struc.fields {
148            writeln!(
149                writer,
150                "    this.{} = {}",
151                field.name.mixed_case(conv),
152                field.name.mixed_case(conv)
153            )?;
154        }
155        writeln!(writer, "}}")?;
156    }
157
158    // Reading
159    writeln!(writer, "companion object {{")?;
160    writer.inc_ident();
161    if let Some((_, discriminant)) = base {
162        writeln!(writer, "val TAG = {}", discriminant)?;
163    }
164    writeln!(writer, "@Throws(java.io.IOException::class)")?;
165    writeln!(
166        writer,
167        "fun readFrom(stream: java.io.InputStream): {} {{",
168        struc.name.camel_case(conv),
169    )?;
170    writer.inc_ident();
171    writeln!(writer, "val result = {}()", struc.name.camel_case(conv),)?;
172    for field in &struc.fields {
173        fn assign(
174            writer: &mut Writer,
175            to: &str,
176            schema: &Schema,
177            index_var: &mut usize,
178        ) -> std::fmt::Result {
179            match schema {
180                Schema::Bool => {
181                    writeln!(writer, "{} = StreamUtil.readBoolean(stream)", to)?;
182                }
183                Schema::Int32 => {
184                    writeln!(writer, "{} = StreamUtil.readInt(stream)", to)?;
185                }
186                Schema::Int64 => {
187                    writeln!(writer, "{} = StreamUtil.readLong(stream)", to)?;
188                }
189                Schema::Float32 => {
190                    writeln!(writer, "{} = StreamUtil.readFloat(stream)", to)?;
191                }
192                Schema::Float64 => {
193                    writeln!(writer, "{} = StreamUtil.readDouble(stream)", to)?;
194                }
195                Schema::String => {
196                    writeln!(writer, "{} = StreamUtil.readString(stream)", to)?;
197                }
198                Schema::Struct(Struct { name, .. })
199                | Schema::OneOf {
200                    base_name: name, ..
201                } => {
202                    writeln!(
203                        writer,
204                        "{} = model.{}.readFrom(stream)",
205                        to,
206                        name.camel_case(conv)
207                    )?;
208                }
209                Schema::Option(inner) => {
210                    writeln!(writer, "if (StreamUtil.readBoolean(stream)) {{")?;
211                    writer.inc_ident();
212                    assign(writer, to, inner, index_var)?;
213                    writer.dec_ident();
214                    writeln!(writer, "}} else {{")?;
215                    writeln!(writer, "    {} = null", to)?;
216                    writeln!(writer, "}}")?;
217                }
218                Schema::Vec(inner) => {
219                    writeln!(writer, "{} = Array(StreamUtil.readInt(stream), {{", to)?;
220                    writer.inc_ident();
221                    writeln!(writer, "var {}Value: {}", var_name(to), type_name(inner))?;
222                    assign(writer, &format!("{}Value", var_name(to)), inner, index_var)?;
223                    writeln!(writer, "{}Value", var_name(to))?;
224                    writer.dec_ident();
225                    writeln!(writer, "}})")?;
226                }
227                Schema::Map(key_type, value_type) => {
228                    let to_size = format!("{}Size", var_name(to));
229                    writeln!(writer, "val {} = StreamUtil.readInt(stream)", to_size)?;
230                    writeln!(writer, "{} = mutableMapOf()", to)?;
231                    let index_var_name = index_var_name(index_var);
232                    writeln!(writer, "for ({} in 0 until {}) {{", index_var_name, to_size)?;
233                    writer.inc_ident();
234                    writeln!(writer, "var {}Key: {}", var_name(to), type_name(key_type))?;
235                    assign(writer, &format!("{}Key", var_name(to)), key_type, index_var)?;
236                    writeln!(
237                        writer,
238                        "var {}Value: {}",
239                        var_name(to),
240                        type_name(value_type)
241                    )?;
242                    assign(
243                        writer,
244                        &format!("{}Value", var_name(to)),
245                        value_type,
246                        index_var,
247                    )?;
248                    writeln!(
249                        writer,
250                        "{}.put({}Key, {}Value)",
251                        to,
252                        var_name(to),
253                        var_name(to)
254                    )?;
255                    writer.dec_ident();
256                    writeln!(writer, "}}")?;
257                }
258                Schema::Enum {
259                    base_name,
260                    variants,
261                } => {
262                    writeln!(writer, "when (StreamUtil.readInt(stream)) {{")?;
263                    for (discriminant, variant) in variants.iter().enumerate() {
264                        write!(writer, "{} ->", discriminant)?;
265                        writeln!(
266                            writer,
267                            "{} = model.{}.{}",
268                            to,
269                            base_name.camel_case(conv),
270                            variant.shouty_snake_case(conv)
271                        )?;
272                    }
273                    writeln!(
274                        writer,
275                        "else -> throw java.io.IOException(\"Unexpected discriminant value\")"
276                    )?;
277                    writeln!(writer, "}}")?;
278                }
279            }
280            Ok(())
281        }
282        assign(
283            writer,
284            &format!("result.{}", field.name.mixed_case(conv)),
285            &field.schema,
286            &mut 0,
287        )?;
288    }
289    writeln!(writer, "return result")?;
290    writer.dec_ident();
291    writeln!(writer, "}}")?;
292    writer.dec_ident();
293    writeln!(writer, "}}")?;
294
295    // Writing
296    writeln!(writer, "@Throws(java.io.IOException::class)")?;
297    writeln!(
298        writer,
299        "{}fun writeTo(stream: java.io.OutputStream) {{",
300        if base.is_some() { "override " } else { "" }
301    )?;
302    writer.inc_ident();
303    if base.is_some() {
304        writeln!(writer, "StreamUtil.writeInt(stream, TAG)")?;
305    }
306    if let Some(magic) = struc.magic {
307        writeln!(writer, "StreamUtil.writeInt(stream, {})", magic)?;
308    }
309    for field in &struc.fields {
310        fn write(writer: &mut Writer, value: &str, schema: &Schema) -> std::fmt::Result {
311            match schema {
312                Schema::Bool => {
313                    writeln!(writer, "StreamUtil.writeBoolean(stream, {})", value)?;
314                }
315                Schema::Int32 => {
316                    writeln!(writer, "StreamUtil.writeInt(stream, {})", value)?;
317                }
318                Schema::Int64 => {
319                    writeln!(writer, "StreamUtil.writeLong(stream, {})", value)?;
320                }
321                Schema::Float32 => {
322                    writeln!(writer, "StreamUtil.writeFloat(stream, {})", value)?;
323                }
324                Schema::Float64 => {
325                    writeln!(writer, "StreamUtil.writeDouble(stream, {})", value)?;
326                }
327                Schema::String => {
328                    writeln!(writer, "StreamUtil.writeString(stream, {})", value)?;
329                }
330                Schema::Struct(_) | Schema::OneOf { .. } => {
331                    writeln!(writer, "{}.writeTo(stream)", value)?;
332                }
333                Schema::Option(inner) => {
334                    writeln!(writer, "val {} = {};", var_name(value), value)?;
335                    writeln!(writer, "if ({} == null) {{", var_name(value))?;
336                    writeln!(writer, "    StreamUtil.writeBoolean(stream, false)")?;
337                    writeln!(writer, "}} else {{")?;
338                    writer.inc_ident();
339                    writeln!(writer, "StreamUtil.writeBoolean(stream, true)")?;
340                    write(writer, &var_name(value), inner)?;
341                    writer.dec_ident();
342                    writeln!(writer, "}}")?;
343                }
344                Schema::Vec(inner) => {
345                    writeln!(writer, "StreamUtil.writeInt(stream, {}.size)", value)?;
346                    writeln!(writer, "for ({}Element in {}) {{", var_name(value), value)?;
347                    writer.inc_ident();
348                    write(writer, &format!("{}Element", var_name(value)), inner)?;
349                    writer.dec_ident();
350                    writeln!(writer, "}}")?;
351                }
352                Schema::Map(key_type, value_type) => {
353                    writeln!(writer, "StreamUtil.writeInt(stream, {}.size)", value)?;
354                    writeln!(writer, "for ({}Entry in {}) {{", var_name(value), value,)?;
355                    writer.inc_ident();
356                    write(writer, &format!("{}Entry.key", var_name(value)), key_type)?;
357                    write(
358                        writer,
359                        &format!("{}Entry.value", var_name(value)),
360                        value_type,
361                    )?;
362                    writer.dec_ident();
363                    writeln!(writer, "}}")?;
364                }
365                Schema::Enum { .. } => {
366                    writeln!(
367                        writer,
368                        "StreamUtil.writeInt(stream, {}.discriminant)",
369                        value
370                    )?;
371                }
372            }
373            Ok(())
374        }
375        write(writer, &field.name.mixed_case(conv), &field.schema)?;
376    }
377    writer.dec_ident();
378    writeln!(writer, "}}")?;
379    writer.dec_ident();
380    writeln!(writer, "}}")?;
381    Ok(())
382}
383
384impl trans_gen_core::Generator for Generator {
385    fn new(name: &str, version: &str) -> Self {
386        let mut files = HashMap::new();
387        files.insert(
388            "util/StreamUtil.kt".to_owned(),
389            include_str!("../template/StreamUtil.kt").to_owned(),
390        );
391        Self { files }
392    }
393    fn result(self) -> HashMap<String, String> {
394        self.files
395    }
396    fn add_only(&mut self, schema: &Schema) {
397        match schema {
398            Schema::Enum {
399                base_name,
400                variants,
401            } => {
402                let file_name = format!("model/{}.kt", base_name.camel_case(conv));
403                let mut writer = Writer::new();
404                writeln!(writer, "package model").unwrap();
405                writeln!(writer).unwrap();
406                writeln!(writer, "import util.StreamUtil").unwrap();
407                writeln!(writer).unwrap();
408                writeln!(
409                    writer,
410                    "enum class {} private constructor(var discriminant: Int) {{",
411                    base_name.camel_case(conv)
412                )
413                .unwrap();
414                writer.inc_ident();
415                for (index, variant) in variants.iter().enumerate() {
416                    writeln!(
417                        writer,
418                        "{}({}){}",
419                        variant.shouty_snake_case(conv),
420                        index,
421                        if index + 1 < variants.len() { "," } else { "" }
422                    )
423                    .unwrap();
424                }
425                writer.dec_ident();
426                writeln!(writer, "}}").unwrap();
427                self.files.insert(file_name, writer.get());
428            }
429            Schema::Struct(struc) => {
430                let file_name = format!("model/{}.kt", struc.name.camel_case(conv));
431                let mut writer = Writer::new();
432                writeln!(writer, "package model").unwrap();
433                writeln!(writer).unwrap();
434                writeln!(writer, "import util.StreamUtil").unwrap();
435                writeln!(writer).unwrap();
436                write_struct(&mut writer, struc, None).unwrap();
437                self.files.insert(file_name, writer.get());
438            }
439            Schema::OneOf {
440                base_name,
441                variants,
442            } => {
443                let file_name = format!("model/{}.kt", base_name.camel_case(conv));
444                let mut writer = Writer::new();
445                writeln!(writer, "package model").unwrap();
446                writeln!(writer).unwrap();
447                writeln!(writer, "import util.StreamUtil").unwrap();
448                writeln!(writer).unwrap();
449                writeln!(writer, "abstract class {} {{", base_name.camel_case(conv)).unwrap();
450                {
451                    writer.inc_ident();
452                    writeln!(writer, "@Throws(java.io.IOException::class)").unwrap();
453                    writeln!(writer, "abstract fun writeTo(stream: java.io.OutputStream)").unwrap();
454                    writeln!(writer, "companion object {{").unwrap();
455                    writer.inc_ident();
456                    writeln!(writer, "@Throws(java.io.IOException::class)").unwrap();
457                    writeln!(
458                        writer,
459                        "fun readFrom(stream: java.io.InputStream): {} {{",
460                        base_name.camel_case(conv)
461                    )
462                    .unwrap();
463                    {
464                        writer.inc_ident();
465                        writeln!(writer, "when (StreamUtil.readInt(stream)) {{").unwrap();
466                        writer.inc_ident();
467                        for variant in variants {
468                            write!(writer, "{}.TAG -> ", variant.name.camel_case(conv)).unwrap();
469                            writeln!(
470                                writer,
471                                "return {}.readFrom(stream)",
472                                variant.name.camel_case(conv)
473                            )
474                            .unwrap();
475                        }
476                        writeln!(
477                            writer,
478                            "else -> throw java.io.IOException(\"Unexpected discriminant value\")"
479                        )
480                        .unwrap();
481                        writer.dec_ident();
482                        writeln!(writer, "}}").unwrap();
483                        writer.dec_ident();
484                    }
485                    writeln!(writer, "}}").unwrap();
486                    writer.dec_ident();
487                    writeln!(writer, "}}").unwrap();
488                    for (discriminant, variant) in variants.iter().enumerate() {
489                        writeln!(writer).unwrap();
490                        write_struct(&mut writer, variant, Some((base_name, discriminant)))
491                            .unwrap();
492                    }
493                    writer.dec_ident();
494                }
495                writeln!(writer, "}}").unwrap();
496                self.files.insert(file_name, writer.get());
497            }
498            Schema::Bool
499            | Schema::Int32
500            | Schema::Int64
501            | Schema::Float32
502            | Schema::Float64
503            | Schema::String
504            | Schema::Option(_)
505            | Schema::Vec(_)
506            | Schema::Map(_, _) => {}
507        }
508    }
509}