trans_gen_scala/
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!("Option[{}]", type_name(inner)),
34        Schema::Vec(inner) => format!("Seq[{}]", type_name(inner)),
35        Schema::Map(key, value) => format!("Map[{}, {}]", type_name(key), type_name(value)),
36    }
37}
38
39fn write_struct(
40    writer: &mut Writer,
41    struc: &Struct,
42    base: Option<(&Name, usize)>,
43) -> std::fmt::Result {
44    // Class
45    write!(writer, "case class {}(", struc.name.camel_case(conv))?;
46    for (index, field) in struc.fields.iter().enumerate() {
47        if index > 0 {
48            write!(writer, ", ")?;
49        }
50        write!(
51            writer,
52            "{}: {}",
53            field.name.mixed_case(conv),
54            type_name(&field.schema),
55        )?;
56    }
57    write!(writer, ")")?;
58    if let Some((base, _)) = base {
59        write!(writer, " extends {}", base.camel_case(conv))?;
60    }
61    writeln!(writer, " {{")?;
62    writer.inc_ident();
63
64    // Writing
65    if base.is_some() {
66        write!(writer, "override ")?;
67    }
68    writeln!(writer, "def writeTo(stream: java.io.OutputStream) {{")?;
69    writer.inc_ident();
70    if base.is_some() {
71        writeln!(
72            writer,
73            "StreamUtil.writeInt(stream, {}.TAG)",
74            struc.name.camel_case(conv),
75        )?;
76    }
77    if let Some(magic) = struc.magic {
78        writeln!(writer, "StreamUtil.writeInt(stream, {})", magic)?;
79    }
80    for field in &struc.fields {
81        fn write(writer: &mut Writer, value: &str, schema: &Schema) -> std::fmt::Result {
82            match schema {
83                Schema::Bool => {
84                    writeln!(writer, "StreamUtil.writeBoolean(stream, {})", value)?;
85                }
86                Schema::Int32 => {
87                    writeln!(writer, "StreamUtil.writeInt(stream, {})", value)?;
88                }
89                Schema::Int64 => {
90                    writeln!(writer, "StreamUtil.writeLong(stream, {})", value)?;
91                }
92                Schema::Float32 => {
93                    writeln!(writer, "StreamUtil.writeFloat(stream, {})", value)?;
94                }
95                Schema::Float64 => {
96                    writeln!(writer, "StreamUtil.writeDouble(stream, {})", value)?;
97                }
98                Schema::String => {
99                    writeln!(writer, "StreamUtil.writeString(stream, {})", value)?;
100                }
101                Schema::Struct(_) | Schema::OneOf { .. } | Schema::Enum { .. } => {
102                    writeln!(writer, "{}.writeTo(stream)", value)?;
103                }
104                Schema::Option(inner) => {
105                    writeln!(writer, "{} match {{", value)?;
106                    writer.inc_ident();
107                    writeln!(
108                        writer,
109                        "case None => StreamUtil.writeBoolean(stream, false)"
110                    )?;
111                    writeln!(writer, "case Some(value) => {{")?;
112                    writer.inc_ident();
113                    writeln!(writer, "StreamUtil.writeBoolean(stream, true)")?;
114                    write(writer, "value", inner)?;
115                    writer.dec_ident();
116                    writeln!(writer, "}}")?;
117                    writer.dec_ident();
118                    writeln!(writer, "}}")?;
119                }
120                Schema::Vec(inner) => {
121                    writeln!(writer, "StreamUtil.writeInt(stream, {}.length)", value)?;
122                    writeln!(writer, "{}.foreach {{ value =>", value)?;
123                    writer.inc_ident();
124                    write(writer, "value", inner)?;
125                    writer.dec_ident();
126                    writeln!(writer, "}}")?;
127                }
128                Schema::Map(key_type, value_type) => {
129                    writeln!(writer, "StreamUtil.writeInt(stream, {}.size)", value)?;
130                    writeln!(writer, "{}.foreach {{ case (key, value) =>", value)?;
131                    writer.inc_ident();
132                    write(writer, "key", key_type)?;
133                    write(writer, "value", value_type)?;
134                    writer.dec_ident();
135                    writeln!(writer, "}}")?;
136                }
137            }
138            Ok(())
139        }
140        write(writer, &field.name.mixed_case(conv), &field.schema)?;
141    }
142    writer.dec_ident();
143    writeln!(writer, "}}")?;
144
145    writer.dec_ident();
146    writeln!(writer, "}}")?;
147
148    // Object
149    writeln!(writer, "object {} {{", struc.name.camel_case(conv))?;
150    writer.inc_ident();
151    if let Some((_, discriminant)) = base {
152        writeln!(writer, "val TAG: Int = {}", discriminant)?;
153    }
154
155    // Reading
156    writeln!(
157        writer,
158        "def readFrom(stream: java.io.InputStream): {} = {}(",
159        struc.name.camel_case(conv),
160        struc.name.camel_case(conv),
161    )?;
162    writer.inc_ident();
163    for (index, field) in struc.fields.iter().enumerate() {
164        fn read(writer: &mut Writer, schema: &Schema) -> std::fmt::Result {
165            match schema {
166                Schema::Bool => {
167                    writeln!(writer, "StreamUtil.readBoolean(stream)")?;
168                }
169                Schema::Int32 => {
170                    writeln!(writer, "StreamUtil.readInt(stream)")?;
171                }
172                Schema::Int64 => {
173                    writeln!(writer, "StreamUtil.readLong(stream)")?;
174                }
175                Schema::Float32 => {
176                    writeln!(writer, "StreamUtil.readFloat(stream)")?;
177                }
178                Schema::Float64 => {
179                    writeln!(writer, "StreamUtil.readDouble(stream)")?;
180                }
181                Schema::String => {
182                    writeln!(writer, "StreamUtil.readString(stream)")?;
183                }
184                Schema::Struct(Struct { name, .. })
185                | Schema::OneOf {
186                    base_name: name, ..
187                }
188                | Schema::Enum {
189                    base_name: name, ..
190                } => {
191                    writeln!(writer, "model.{}.readFrom(stream)", name.camel_case(conv))?;
192                }
193                Schema::Option(inner) => {
194                    writeln!(writer, "if (StreamUtil.readBoolean(stream)) Some(")?;
195                    writer.inc_ident();
196                    read(writer, inner)?;
197                    writer.dec_ident();
198                    writeln!(writer, ") else None")?;
199                }
200                Schema::Vec(inner) => {
201                    writeln!(writer, "(0 until StreamUtil.readInt(stream)).map {{ _ =>",)?;
202                    writer.inc_ident();
203                    read(writer, inner)?;
204                    writer.dec_ident();
205                    writeln!(writer, "}}")?;
206                }
207                Schema::Map(key_type, value_type) => {
208                    writeln!(writer, "(0 until StreamUtil.readInt(stream)).map {{ _ => (",)?;
209                    writer.inc_ident();
210                    read(writer, key_type)?;
211                    writeln!(writer, ",")?;
212                    read(writer, value_type)?;
213                    writer.dec_ident();
214                    writeln!(writer, ")}}.toMap")?;
215                }
216            }
217            Ok(())
218        }
219        read(writer, &field.schema)?;
220        if index + 1 < struc.fields.len() {
221            writeln!(writer, ",")?;
222        }
223    }
224    writeln!(writer, ")")?;
225    writer.dec_ident();
226    writer.dec_ident();
227    writeln!(writer, "}}")?;
228    Ok(())
229}
230
231impl trans_gen_core::Generator for Generator {
232    fn new(name: &str, version: &str) -> Self {
233        let mut files = HashMap::new();
234        files.insert(
235            "util/StreamUtil.scala".to_owned(),
236            include_str!("../template/StreamUtil.scala").to_owned(),
237        );
238        Self { files }
239    }
240    fn result(self) -> HashMap<String, String> {
241        self.files
242    }
243    fn add_only(&mut self, schema: &Schema) {
244        match schema {
245            Schema::Enum {
246                base_name,
247                variants,
248            } => {
249                let file_name = format!("model/{}.scala", base_name.camel_case(conv));
250                let mut writer = Writer::new();
251                writeln!(writer, "package model").unwrap();
252                writeln!(writer).unwrap();
253                writeln!(writer, "import util.StreamUtil").unwrap();
254                writeln!(writer).unwrap();
255                writeln!(
256                    writer,
257                    "sealed abstract class {} (val discriminant: Int) {{",
258                    base_name.camel_case(conv)
259                )
260                .unwrap();
261                writer.inc_ident();
262                writeln!(writer, "def writeTo(stream: java.io.OutputStream) {{").unwrap();
263                writeln!(writer, "    StreamUtil.writeInt(stream, discriminant)").unwrap();
264                writeln!(writer, "}}").unwrap();
265                writer.dec_ident();
266                writeln!(writer, "}}").unwrap();
267                writeln!(writer).unwrap();
268                writeln!(writer, "object {} {{", base_name.camel_case(conv)).unwrap();
269                writer.inc_ident();
270                for (index, variant) in variants.iter().enumerate() {
271                    writeln!(
272                        writer,
273                        "case object {} extends {}({})",
274                        variant.shouty_snake_case(conv),
275                        base_name.camel_case(conv),
276                        index,
277                    )
278                    .unwrap();
279                }
280                writeln!(
281                    writer,
282                    "def readFrom(stream: java.io.InputStream): {} = StreamUtil.readInt(stream) match {{",
283                    base_name.camel_case(conv)
284                )
285                .unwrap();
286                writer.inc_ident();
287                for (index, variant) in variants.iter().enumerate() {
288                    writeln!(
289                        writer,
290                        "case {} => {}",
291                        index,
292                        variant.shouty_snake_case(conv),
293                    )
294                    .unwrap();
295                }
296                writeln!(
297                    writer,
298                    "case _ => throw new java.io.IOException(\"Unexpected discriminant value\")"
299                )
300                .unwrap();
301                writer.dec_ident();
302                writeln!(writer, "}}").unwrap();
303                writer.dec_ident();
304                writeln!(writer, "}}").unwrap();
305                self.files.insert(file_name, writer.get());
306            }
307            Schema::Struct(struc) => {
308                let file_name = format!("model/{}.scala", struc.name.camel_case(conv));
309                let mut writer = Writer::new();
310                writeln!(writer, "package model").unwrap();
311                writeln!(writer).unwrap();
312                writeln!(writer, "import util.StreamUtil").unwrap();
313                writeln!(writer).unwrap();
314                write_struct(&mut writer, struc, None).unwrap();
315                self.files.insert(file_name, writer.get());
316            }
317            Schema::OneOf {
318                base_name,
319                variants,
320            } => {
321                let file_name = format!("model/{}.scala", base_name.camel_case(conv));
322                let mut writer = Writer::new();
323                writeln!(writer, "package model").unwrap();
324                writeln!(writer).unwrap();
325                writeln!(writer, "import util.StreamUtil").unwrap();
326                writeln!(writer).unwrap();
327                writeln!(writer, "sealed trait {} {{", base_name.camel_case(conv)).unwrap();
328                writeln!(writer, "    def writeTo(stream: java.io.OutputStream)").unwrap();
329                writeln!(writer, "}}").unwrap();
330                writeln!(&mut writer, "object {} {{", base_name.camel_case(conv)).unwrap();
331                {
332                    writer.inc_ident();
333                    for (discriminant, variant) in variants.iter().enumerate() {
334                        write_struct(&mut writer, variant, Some((base_name, discriminant)))
335                            .unwrap();
336                        writeln!(&mut writer).unwrap();
337                    }
338                    writeln!(
339                        &mut writer,
340                        "def readFrom(stream: java.io.InputStream): {} = {{",
341                        base_name.camel_case(conv)
342                    )
343                    .unwrap();
344                    {
345                        writer.inc_ident();
346                        writeln!(&mut writer, "StreamUtil.readInt(stream) match {{").unwrap();
347                        writer.inc_ident();
348                        for variant in variants {
349                            writeln!(
350                                &mut writer,
351                                "case {}.TAG => {}.readFrom(stream)",
352                                variant.name.camel_case(conv),
353                                variant.name.camel_case(conv),
354                            )
355                            .unwrap();
356                        }
357                        writeln!(
358                            &mut writer,
359                            "case _ => throw new java.io.IOException(\"Unexpected discriminant value\")"
360                        )
361                        .unwrap();
362                        writer.dec_ident();
363                        writeln!(&mut writer, "}}").unwrap();
364                        writer.dec_ident();
365                    }
366                    writeln!(&mut writer, "}}").unwrap();
367                    writer.dec_ident();
368                }
369                writeln!(&mut writer, "}}").unwrap();
370                self.files.insert(file_name, writer.get());
371            }
372            Schema::Bool
373            | Schema::Int32
374            | Schema::Int64
375            | Schema::Float32
376            | Schema::Float64
377            | Schema::String
378            | Schema::Option(_)
379            | Schema::Vec(_)
380            | Schema::Map(_, _) => {}
381        }
382    }
383}