trans_gen_python/
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("Int32", "Int")
8        .replace("Int64", "Long")
9        .replace("Float32", "Float")
10        .replace("Float64", "Double")
11}
12
13pub struct Generator {
14    model_init: String,
15    files: HashMap<String, String>,
16}
17
18fn write_struct(
19    writer: &mut Writer,
20    struc: &Struct,
21    base: Option<(&Name, usize)>,
22) -> std::fmt::Result {
23    // Imports
24    for field in &struc.fields {
25        fn add_imports(writer: &mut Writer, schema: &Schema) -> std::fmt::Result {
26            match schema {
27                Schema::Struct(Struct { name, .. })
28                | Schema::OneOf {
29                    base_name: name, ..
30                }
31                | Schema::Enum {
32                    base_name: name, ..
33                } => {
34                    writeln!(
35                        writer,
36                        "from .{} import {}",
37                        name.snake_case(conv),
38                        name.camel_case(conv)
39                    )?;
40                }
41                Schema::Option(inner) => {
42                    add_imports(writer, inner)?;
43                }
44                Schema::Vec(inner) => {
45                    add_imports(writer, inner)?;
46                }
47                Schema::Map(key_type, value_type) => {
48                    add_imports(writer, key_type)?;
49                    add_imports(writer, value_type)?;
50                }
51                Schema::Bool
52                | Schema::Int32
53                | Schema::Int64
54                | Schema::Float32
55                | Schema::Float64
56                | Schema::String => {}
57            }
58            Ok(())
59        }
60        add_imports(writer, &field.schema)?;
61    }
62
63    // Class
64    write!(writer, "class {}", struc.name.camel_case(conv))?;
65    if let Some((base_name, _)) = base {
66        write!(writer, "({})", base_name.camel_case(conv))?;
67    }
68    writeln!(writer, ":")?;
69    writer.inc_ident();
70    if let Some((_, discriminant)) = base {
71        writeln!(writer, "TAG = {}", discriminant)?;
72    }
73
74    // Constructor
75    write!(writer, "def __init__(self")?;
76    for field in &struc.fields {
77        write!(writer, ", {}", field.name.snake_case(conv))?;
78    }
79    writeln!(writer, "):")?;
80    for field in &struc.fields {
81        writeln!(
82            writer,
83            "    self.{} = {}",
84            field.name.snake_case(conv),
85            field.name.snake_case(conv)
86        )?;
87    }
88    if struc.fields.is_empty() {
89        writeln!(writer, "    pass")?;
90    }
91
92    // Reading
93    writeln!(writer, "@staticmethod")?;
94    writeln!(writer, "def read_from(stream):")?;
95    writer.inc_ident();
96    for field in &struc.fields {
97        fn assign(writer: &mut Writer, to: &str, schema: &Schema) -> std::fmt::Result {
98            match schema {
99                Schema::Bool => {
100                    writeln!(writer, "{} = stream.read_bool()", to)?;
101                }
102                Schema::Int32 => {
103                    writeln!(writer, "{} = stream.read_int()", to)?;
104                }
105                Schema::Int64 => {
106                    writeln!(writer, "{} = stream.read_long()", to)?;
107                }
108                Schema::Float32 => {
109                    writeln!(writer, "{} = stream.read_float()", to)?;
110                }
111                Schema::Float64 => {
112                    writeln!(writer, "{} = stream.read_double()", to)?;
113                }
114                Schema::String => {
115                    writeln!(writer, "{} = stream.read_string()", to)?;
116                }
117                Schema::Struct(Struct { name, .. })
118                | Schema::OneOf {
119                    base_name: name, ..
120                } => {
121                    writeln!(
122                        writer,
123                        "{} = {}.read_from(stream)",
124                        to,
125                        name.camel_case(conv)
126                    )?;
127                }
128                Schema::Option(inner) => {
129                    writeln!(writer, "if stream.read_bool():")?;
130                    writer.inc_ident();
131                    assign(writer, to, inner)?;
132                    writer.dec_ident();
133                    writeln!(writer, "else:")?;
134                    writeln!(writer, "    {} = None", to)?;
135                }
136                Schema::Vec(inner) => {
137                    writeln!(writer, "{} = []", to)?;
138                    writeln!(writer, "for _ in range(stream.read_int()):")?;
139                    writer.inc_ident();
140                    assign(writer, &format!("{}_element", to), inner)?;
141                    writeln!(writer, "{}.append({}_element)", to, to)?;
142                    writer.dec_ident();
143                }
144                Schema::Map(key_type, value_type) => {
145                    writeln!(writer, "{} = {{}}", to)?;
146                    writeln!(writer, "for _ in range(stream.read_int()):")?;
147                    writer.inc_ident();
148                    assign(writer, &format!("{}_key", to), key_type)?;
149                    assign(writer, &format!("{}_value", to), value_type)?;
150                    writeln!(writer, "{}[{}_key] = {}_value", to, to, to)?;
151                    writer.dec_ident();
152                }
153                Schema::Enum { base_name, .. } => {
154                    writeln!(
155                        writer,
156                        "{} = {}(stream.read_int())",
157                        to,
158                        base_name.camel_case(conv)
159                    )?;
160                }
161            }
162            Ok(())
163        }
164        assign(writer, &field.name.snake_case(conv), &field.schema)?;
165    }
166    write!(writer, "return {}(", struc.name.camel_case(conv))?;
167    let mut first = true;
168    for field in &struc.fields {
169        if first {
170            first = false;
171        } else {
172            write!(writer, ", ")?;
173        }
174        write!(writer, "{}", field.name.snake_case(conv))?;
175    }
176    writeln!(writer, ")")?;
177    writer.dec_ident();
178
179    // Writing
180    writeln!(writer, "def write_to(self, stream):")?;
181    writer.inc_ident();
182    if base.is_some() {
183        writeln!(writer, "stream.write_int(self.TAG)")?;
184    } else if struc.fields.is_empty() && struc.magic.is_none() {
185        writeln!(writer, "pass")?;
186    }
187    if let Some(magic) = struc.magic {
188        writeln!(writer, "stream.write_int({})", magic)?;
189    }
190    for field in &struc.fields {
191        fn write(writer: &mut Writer, value: &str, schema: &Schema) -> std::fmt::Result {
192            match schema {
193                Schema::Bool => {
194                    writeln!(writer, "stream.write_bool({})", value)?;
195                }
196                Schema::Int32 => {
197                    writeln!(writer, "stream.write_int({})", value)?;
198                }
199                Schema::Int64 => {
200                    writeln!(writer, "stream.write_long({})", value)?;
201                }
202                Schema::Float32 => {
203                    writeln!(writer, "stream.write_float({})", value)?;
204                }
205                Schema::Float64 => {
206                    writeln!(writer, "stream.write_double({})", value)?;
207                }
208                Schema::String => {
209                    writeln!(writer, "stream.write_string({})", value)?;
210                }
211                Schema::Struct(_) | Schema::OneOf { .. } => {
212                    writeln!(writer, "{}.write_to(stream)", value)?;
213                }
214                Schema::Option(inner) => {
215                    writeln!(writer, "if {} is None:", value)?;
216                    writeln!(writer, "    stream.write_bool(False)")?;
217                    writeln!(writer, "else:")?;
218                    writer.inc_ident();
219                    writeln!(writer, "stream.write_bool(True)")?;
220                    write(writer, value, inner)?;
221                    writer.dec_ident();
222                }
223                Schema::Vec(inner) => {
224                    writeln!(writer, "stream.write_int(len({}))", value)?;
225                    writeln!(writer, "for element in {}:", value)?;
226                    writer.inc_ident();
227                    write(writer, "element", inner)?;
228                    writer.dec_ident();
229                }
230                Schema::Map(key_type, value_type) => {
231                    writeln!(writer, "stream.write_int(len({}))", value)?;
232                    writeln!(writer, "for key, value in {}.items():", value)?;
233                    writer.inc_ident();
234                    write(writer, "key", key_type)?;
235                    write(writer, "value", value_type)?;
236                    writer.dec_ident();
237                }
238                Schema::Enum { .. } => {
239                    writeln!(writer, "stream.write_int({})", value)?;
240                }
241            }
242            Ok(())
243        }
244        write(
245            writer,
246            &format!("self.{}", field.name.snake_case(conv)),
247            &field.schema,
248        )?;
249    }
250    writer.dec_ident();
251
252    // Repr
253    writeln!(writer, "def __repr__(self):")?;
254    writer.inc_ident();
255    writeln!(writer, "return \"{}(\" + \\", struc.name.camel_case(conv))?;
256    for (index, field) in struc.fields.iter().enumerate() {
257        write!(writer, "    repr(self.{})", field.name.snake_case(conv))?;
258        if index + 1 < struc.fields.len() {
259            write!(writer, " + \",\"")?;
260        }
261        writeln!(writer, " + \\")?;
262    }
263    writeln!(writer, "    \")\"")?;
264    writer.dec_ident();
265
266    writer.dec_ident();
267
268    if let Some((base_name, _)) = base {
269        writeln!(
270            writer,
271            "{}.{} = {}",
272            base_name.camel_case(conv),
273            struc.name.camel_case(conv),
274            struc.name.camel_case(conv)
275        )?;
276    }
277
278    Ok(())
279}
280
281impl trans_gen_core::Generator for Generator {
282    fn new(name: &str, version: &str) -> Self {
283        let mut files = HashMap::new();
284        files.insert(
285            "stream_wrapper.py".to_owned(),
286            include_str!("../template/stream_wrapper.py").to_owned(),
287        );
288        Self {
289            model_init: String::new(),
290            files,
291        }
292    }
293    fn result(mut self) -> HashMap<String, String> {
294        if !self.model_init.is_empty() {
295            self.files
296                .insert("model/__init__.py".to_owned(), self.model_init);
297        }
298        self.files
299    }
300    fn add_only(&mut self, schema: &Schema) {
301        match schema {
302            Schema::Enum {
303                base_name,
304                variants,
305            } => {
306                let file_name = format!("model/{}.py", base_name.snake_case(conv));
307                let mut writer = Writer::new();
308                writeln!(writer, "from enum import IntEnum").unwrap();
309                writeln!(writer).unwrap();
310                writeln!(writer, "class {}(IntEnum):", base_name.camel_case(conv)).unwrap();
311                writer.inc_ident();
312                for (index, variant) in variants.iter().enumerate() {
313                    writeln!(writer, "{} = {}", variant.shouty_snake_case(conv), index).unwrap();
314                }
315                writer.dec_ident();
316                writeln!(
317                    &mut self.model_init,
318                    "from .{} import {}",
319                    base_name.snake_case(conv),
320                    base_name.camel_case(conv),
321                )
322                .unwrap();
323                self.files.insert(file_name, writer.get());
324            }
325            Schema::Struct(struc) => {
326                let file_name = format!("model/{}.py", struc.name.snake_case(conv));
327                let mut writer = Writer::new();
328                write_struct(&mut writer, struc, None).unwrap();
329                writeln!(
330                    &mut self.model_init,
331                    "from .{} import {}",
332                    struc.name.snake_case(conv),
333                    struc.name.camel_case(conv)
334                )
335                .unwrap();
336                self.files.insert(file_name, writer.get());
337            }
338            Schema::OneOf {
339                base_name,
340                variants,
341            } => {
342                let file_name = format!("model/{}.py", base_name.snake_case(conv));
343                let mut writer = Writer::new();
344                writeln!(&mut writer, "class {}:", base_name.camel_case(conv)).unwrap();
345                {
346                    writer.inc_ident();
347                    writeln!(&mut writer, "@staticmethod").unwrap();
348                    writeln!(&mut writer, "def read_from(stream):").unwrap();
349                    {
350                        writer.inc_ident();
351                        writeln!(&mut writer, "discriminant = stream.read_int()").unwrap();
352                        for variant in variants {
353                            writeln!(
354                                &mut writer,
355                                "if discriminant == {}.TAG:",
356                                variant.name.camel_case(conv)
357                            )
358                            .unwrap();
359                            writeln!(
360                                &mut writer,
361                                "    return {}.{}.read_from(stream)",
362                                base_name.camel_case(conv),
363                                variant.name.camel_case(conv),
364                            )
365                            .unwrap();
366                        }
367                        writeln!(
368                            &mut writer,
369                            "raise Exception(\"Unexpected discriminant value\")"
370                        )
371                        .unwrap();
372                        writer.dec_ident();
373                    }
374                    writer.dec_ident();
375                }
376                writeln!(&mut writer).unwrap();
377                for (discriminant, variant) in variants.iter().enumerate() {
378                    write_struct(&mut writer, variant, Some((base_name, discriminant))).unwrap();
379                }
380                writeln!(
381                    &mut self.model_init,
382                    "from .{} import {}",
383                    base_name.snake_case(conv),
384                    base_name.camel_case(conv)
385                )
386                .unwrap();
387                self.files.insert(file_name, writer.get());
388            }
389            Schema::Bool
390            | Schema::Int32
391            | Schema::Int64
392            | Schema::Float32
393            | Schema::Float64
394            | Schema::String
395            | Schema::Option(_)
396            | Schema::Vec(_)
397            | Schema::Map(_, _) => {}
398        }
399    }
400}