trans_gen/gens/kotlin/
mod.rs

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