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