rstgen/
rust.rs

1//! Specialization for Rust code generation.
2
3use std::collections::BTreeSet;
4use std::fmt::{self, Write};
5use std::rc::Rc;
6use {Cons, Custom, Formatter, IntoTokens, Tokens};
7
8static SEP: &'static str = "::";
9
10/// The inferred reference.
11#[derive(Debug, Clone, Copy)]
12pub struct Ref;
13
14/// The static reference.
15#[derive(Debug, Clone, Copy)]
16pub struct StaticRef;
17
18/// Reference information about a name.
19#[derive(Debug, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)]
20pub enum Reference<'el> {
21    /// An anonymous reference.
22    Ref,
23    /// A static reference.
24    StaticRef,
25    /// A named reference.
26    Named(Cons<'el>),
27}
28
29impl From<Ref> for Reference<'static> {
30    fn from(_: Ref) -> Self {
31        Reference::Ref
32    }
33}
34
35impl From<StaticRef> for Reference<'static> {
36    fn from(_: StaticRef) -> Self {
37        Reference::StaticRef
38    }
39}
40
41impl From<Rc<String>> for Reference<'static> {
42    fn from(value: Rc<String>) -> Self {
43        Reference::Named(Cons::from(value))
44    }
45}
46
47impl<'el> From<&'el str> for Reference<'el> {
48    fn from(value: &'el str) -> Self {
49        Reference::Named(Cons::from(value))
50    }
51}
52
53/// A name.
54#[derive(Debug, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)]
55pub struct Name<'el> {
56    reference: Option<Reference<'el>>,
57    /// Name  of class.
58    name: Cons<'el>,
59    /// Arguments of the class.
60    arguments: Vec<Rust<'el>>,
61}
62
63impl<'el> Name<'el> {
64    /// Format the name.
65    fn format(&self, out: &mut Formatter, extra: &mut (), level: usize) -> fmt::Result {
66        if let Some(reference) = self.reference.as_ref() {
67            match *reference {
68                Reference::StaticRef => {
69                    out.write_str("&'static ")?;
70                }
71                Reference::Named(ref name) => {
72                    out.write_str("&'")?;
73                    out.write_str(name.as_ref())?;
74                    out.write_str(" ")?;
75                }
76                Reference::Ref => {
77                    out.write_str("&")?;
78                }
79            }
80        }
81
82        out.write_str(self.name.as_ref())?;
83
84        if !self.arguments.is_empty() {
85            let mut it = self.arguments.iter().peekable();
86
87            out.write_str("<")?;
88
89            while let Some(n) = it.next() {
90                n.format(out, extra, level + 1)?;
91
92                if it.peek().is_some() {
93                    out.write_str(", ")?;
94                }
95            }
96
97            out.write_str(">")?;
98        }
99
100        Ok(())
101    }
102
103    /// Add generic arguments to the given type.
104    pub fn with_arguments(self, arguments: Vec<Rust<'el>>) -> Name<'el> {
105        Name {
106            arguments: arguments,
107            ..self
108        }
109    }
110
111    /// Create a name with the given reference.
112    pub fn reference<R: Into<Reference<'el>>>(self, reference: R) -> Name<'el> {
113        Name {
114            reference: Some(reference.into()),
115            ..self
116        }
117    }
118}
119
120impl<'el> From<Cons<'el>> for Name<'el> {
121    fn from(value: Cons<'el>) -> Self {
122        Name {
123            reference: None,
124            name: value,
125            arguments: vec![],
126        }
127    }
128}
129
130/// Rust token specialization.
131#[derive(Debug, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)]
132pub struct Rust<'el> {
133    /// Module of the imported name.
134    module: Option<Cons<'el>>,
135    /// Alias of module.
136    alias: Option<Cons<'el>>,
137    /// Name imported.
138    name: Name<'el>,
139    /// Qualified import.
140    qualified: bool,
141}
142
143into_tokens_impl_from!(Rust<'el>, Rust<'el>);
144into_tokens_impl_from!(&'el Rust<'el>, Rust<'el>);
145
146impl<'el> Rust<'el> {
147    fn walk_custom<'a, 'b: 'a>(
148        custom: &'a Rust<'b>,
149        modules: &mut BTreeSet<(Cons<'a>, Option<&'a Cons<'b>>)>,
150    ) {
151        if let Some(module) = custom.module.as_ref() {
152            if custom.qualified {
153                let module = Cons::from(format!("{}::{}", module, custom.name.name.as_ref()));
154                modules.insert((module, custom.alias.as_ref()));
155            } else {
156                modules.insert((Cons::from(module.as_ref()), custom.alias.as_ref()));
157            }
158        }
159
160        for arg in &custom.name.arguments {
161            Self::walk_custom(arg, modules);
162        }
163    }
164
165    fn imports<'a>(tokens: &'a Tokens<'a, Self>) -> Option<Tokens<'a, Self>> {
166        let mut modules = BTreeSet::new();
167
168        for custom in tokens.walk_custom() {
169            Rust::walk_custom(&custom, &mut modules);
170        }
171
172        if modules.is_empty() {
173            return None;
174        }
175
176        let mut out = Tokens::new();
177
178        for (module, alias) in modules {
179            let mut s = Tokens::new();
180
181            s.append("use ");
182            s.append(module);
183
184            if let Some(alias) = alias {
185                s.append(" as ");
186                s.append(alias.as_ref());
187            }
188
189            s.append(";");
190
191            out.push(s);
192        }
193
194        Some(out)
195    }
196
197    /// Alias the given type.
198    pub fn alias<A: Into<Cons<'el>>>(self, alias: A) -> Rust<'el> {
199        Rust {
200            alias: Some(alias.into()),
201            ..self
202        }
203    }
204
205    /// Add generic arguments to the given type.
206    pub fn with_arguments(self, arguments: Vec<Rust<'el>>) -> Rust<'el> {
207        Rust {
208            name: self.name.with_arguments(arguments),
209            ..self
210        }
211    }
212
213    /// Change to be a qualified import.
214    pub fn qualified(self) -> Rust<'el> {
215        Rust {
216            qualified: true,
217            ..self
218        }
219    }
220
221    /// Make the type a reference.
222    pub fn reference<R: Into<Reference<'el>>>(self, reference: R) -> Rust<'el> {
223        Rust {
224            module: self.module,
225            name: self.name.reference(reference),
226            alias: self.alias,
227            qualified: self.qualified,
228        }
229    }
230}
231
232impl<'el> Custom for Rust<'el> {
233    type Extra = ();
234
235    fn format(&self, out: &mut Formatter, extra: &mut Self::Extra, level: usize) -> fmt::Result {
236        if let Some(alias) = self.alias.as_ref() {
237            out.write_str(alias)?;
238            out.write_str(SEP)?;
239        } else if let Some(part) = self.module.as_ref().and_then(|m| m.split(SEP).last()) {
240            out.write_str(part)?;
241            out.write_str(SEP)?;
242        }
243
244        self.name.format(out, extra, level)
245    }
246
247    fn quote_string(out: &mut Formatter, input: &str) -> fmt::Result {
248        out.write_char('"')?;
249
250        for c in input.chars() {
251            match c {
252                '\t' => out.write_str("\\t")?,
253                '\n' => out.write_str("\\n")?,
254                '\r' => out.write_str("\\r")?,
255                '\'' => out.write_str("\\'")?,
256                '"' => out.write_str("\\\"")?,
257                '\\' => out.write_str("\\\\")?,
258                c => out.write_char(c)?,
259            };
260        }
261
262        out.write_char('"')?;
263        Ok(())
264    }
265
266    fn write_file<'a>(
267        tokens: Tokens<'a, Self>,
268        out: &mut Formatter,
269        extra: &mut Self::Extra,
270        level: usize,
271    ) -> fmt::Result {
272        let mut toks: Tokens<Self> = Tokens::new();
273
274        if let Some(imports) = Self::imports(&tokens) {
275            toks.push(imports);
276        }
277
278        toks.push_ref(&tokens);
279        toks.join_line_spacing().format(out, extra, level)
280    }
281}
282
283/// Setup an imported element.
284pub fn imported<'a, M, N>(module: M, name: N) -> Rust<'a>
285where
286    M: Into<Cons<'a>>,
287    N: Into<Cons<'a>>,
288{
289    Rust {
290        module: Some(module.into()),
291        alias: None,
292        name: Name::from(name.into()),
293        qualified: false,
294    }
295}
296
297/// Setup a local element.
298pub fn local<'a, N>(name: N) -> Rust<'a>
299where
300    N: Into<Cons<'a>>,
301{
302    Rust {
303        module: None,
304        alias: None,
305        name: Name::from(name.into()),
306        qualified: false,
307    }
308}
309
310#[cfg(test)]
311mod tests {
312    use super::{imported, local};
313    use quoted::Quoted;
314    use rust::Rust;
315    use tokens::Tokens;
316
317    #[test]
318    fn test_string() {
319        let mut toks: Tokens<Rust> = Tokens::new();
320        toks.append("hello \n world".quoted());
321        let res = toks.to_string();
322
323        assert_eq!(Ok("\"hello \\n world\""), res.as_ref().map(|s| s.as_str()));
324    }
325
326    #[test]
327    fn test_imported() {
328        let dbg = imported("std::fmt", "Debug");
329        let mut toks: Tokens<Rust> = Tokens::new();
330        toks.push(toks!(&dbg));
331
332        assert_eq!(
333            Ok("use std::fmt;\n\nfmt::Debug\n"),
334            toks.to_file().as_ref().map(|s| s.as_str())
335        );
336    }
337
338    #[test]
339    fn test_imported_alias() {
340        let dbg = imported("std::fmt", "Debug").alias("dbg");
341        let mut toks: Tokens<Rust> = Tokens::new();
342        toks.push(toks!(&dbg));
343
344        assert_eq!(
345            Ok("use std::fmt as dbg;\n\ndbg::Debug\n"),
346            toks.to_file().as_ref().map(|s| s.as_str())
347        );
348    }
349
350    #[test]
351    fn test_imported_with_arguments() {
352        let dbg = imported("std::fmt", "Debug")
353            .alias("dbg")
354            .with_arguments(vec![local("T"), local("U")]);
355        let mut toks: Tokens<Rust> = Tokens::new();
356        toks.push(toks!(&dbg));
357
358        assert_eq!(
359            Ok("use std::fmt as dbg;\n\ndbg::Debug<T, U>\n"),
360            toks.to_file().as_ref().map(|s| s.as_str())
361        );
362    }
363}