nandi/rustnimpublic Fork 0
0a375d8ec0bfffe1baadd340864ab1ffa92fdb98
Commits
Clone
git clone https://git.rickub.com/nandi/rustnim.git
git clone ssh://git@rickub.com/nandi/rustnim.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

ty.rs · 278 lines · 10.2 KBRust Blame HistoryRaw
Scaffold rustnim: Rust->Nim transpiler, type mapping 1a218c2 nandi 9h ago1//! Rust type -> Nim type mapping.
2//!
3//! Integer width is preserved exactly. Anything that cannot be represented
4//! faithfully in Nim is reported as an error rather than approximated: a
5//! silently widened integer would change the meaning of wrapping arithmetic,
6//! which is precisely the kind of code people write in Rust.
7
8use syn::{GenericArgument, PathArguments, Type, TypeParamBound};
9
10#[derive(Debug, Clone, PartialEq)]
11pub enum Nim {
12 Prim(String),
13 Seq(Box<Nim>),
14 OpenArray(Box<Nim>),
15 Array(usize, Box<Nim>),
16 Tuple(Vec<Nim>),
17 Named(String, Vec<Nim>),
18 Var(Box<Nim>),
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 7h ago19 /// `impl Fn(A) -> B` / `fn(A) -> B`. Left at Nim's default calling
20 /// convention (`closure`), which accepts both a plain top-level proc and
21 /// a closure that captures -- and Rust's `impl Fn` accepts both too.
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago22 Proc(Vec<Nim>, Box<Nim>),
Scaffold rustnim: Rust->Nim transpiler, type mapping 1a218c2 nandi 9h ago23 Unit,
24}
25
26impl Nim {
27 pub fn render(&self) -> String {
28 match self {
29 Nim::Prim(s) => s.clone(),
30 Nim::Seq(t) => format!("seq[{}]", t.render()),
31 Nim::OpenArray(t) => format!("openArray[{}]", t.render()),
32 Nim::Array(n, t) => format!("array[{}, {}]", n, t.render()),
33 Nim::Tuple(ts) => {
34 let inner: Vec<String> = ts.iter().map(|t| t.render()).collect();
35 format!("({})", inner.join(", "))
36 }
37 Nim::Named(n, args) if args.is_empty() => n.clone(),
38 Nim::Named(n, args) => {
39 let inner: Vec<String> = args.iter().map(|t| t.render()).collect();
40 format!("{}[{}]", n, inner.join(", "))
41 }
42 Nim::Var(t) => format!("var {}", t.render()),
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago43 Nim::Proc(args, ret) => {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago44 // Nim's proc types name their parameters even when the name is
45 // never used.
46 let inner: Vec<String> = args
47 .iter()
48 .enumerate()
49 .map(|(i, t)| format!("a{}: {}", i, t.render()))
50 .collect();
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago51 match &**ret {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 7h ago52 Nim::Unit => format!("proc ({})", inner.join(", ")),
53 r => format!("proc ({}): {}", inner.join(", "), r.render()),
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago54 }
55 }
Scaffold rustnim: Rust->Nim transpiler, type mapping 1a218c2 nandi 9h ago56 Nim::Unit => "void".into(),
57 }
58 }
59
60 /// Owned form: a borrowed slice parameter is `openArray[T]`, but the same
61 /// type in an owned position (a field, a return value) must be `seq[T]`.
62 pub fn owned(self) -> Nim {
63 match self {
64 Nim::OpenArray(t) => Nim::Seq(t),
65 Nim::Var(t) => t.owned(),
66 other => other,
67 }
68 }
69
70 pub fn is_integer(&self) -> bool {
71 matches!(self, Nim::Prim(p) if matches!(p.as_str(),
72 "int8"|"int16"|"int32"|"int64"|"int"|
73 "uint8"|"uint16"|"uint32"|"uint64"|"uint"))
74 }
75
76 pub fn is_unsigned(&self) -> bool {
77 matches!(self, Nim::Prim(p) if p.starts_with("uint"))
78 }
79}
80
81pub fn prim(name: &str) -> Option<Nim> {
82 let mapped = match name {
83 "i8" => "int8",
84 "i16" => "int16",
85 "i32" => "int32",
86 "i64" => "int64",
87 "isize" => "int",
88 "u8" => "uint8",
89 "u16" => "uint16",
90 "u32" => "uint32",
91 "u64" => "uint64",
92 "usize" => "uint",
93 "f32" => "float32",
94 "f64" => "float64",
95 "bool" => "bool",
96 "char" => "Rune",
97 "str" | "String" => "string",
98 _ => return None,
99 };
100 Some(Nim::Prim(mapped.into()))
101}
102
103/// Types we refuse rather than approximate.
104pub fn rejected(name: &str) -> Option<&'static str> {
105 match name {
106 "i128" | "u128" => Some("128-bit integers have no faithful Nim equivalent"),
107 _ => None,
108 }
109}
110
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago111fn ret_ty(r: &syn::ReturnType) -> Result<Nim, String> {
112 match r {
113 syn::ReturnType::Default => Ok(Nim::Unit),
114 syn::ReturnType::Type(_, t) => Ok(map(t)?.owned()),
115 }
116}
117
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago118/// Types from `core::fmt` that must not be confused with a user type of the
119/// same short name. `fmt::Error` and a crate's own `Error` are different
120/// types, and collapsing a path to its last segment would merge them.
121fn std_qualified(p: &syn::Path) -> Option<Nim> {
122 let segs: Vec<String> = p.segments.iter().map(|s| s.ident.to_string()).collect();
123 if segs.len() < 2 {
124 return None;
125 }
126 let (q, name) = (&segs[segs.len() - 2], segs.last()?.as_str());
127 if q != "fmt" {
128 return None;
129 }
130 Some(Nim::Prim(
131 match name {
132 "Error" => "FmtError",
133 "Formatter" => "Formatter",
134 // `fmt::Result` is `Result<(), fmt::Error>`. A formatting impl is
135 // lowered to a proc that returns the formatted string, so the
136 // result type is erased there; this spelling exists so that a
137 // signature mentioning it still maps to something.
138 "Result" => "FmtResult",
139 _ => return None,
140 }
141 .into(),
142 ))
143}
144
Scaffold rustnim: Rust->Nim transpiler, type mapping 1a218c2 nandi 9h ago145pub fn map(t: &Type) -> Result<Nim, String> {
146 match t {
147 Type::Path(p) => {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago148 if let Some(n) = std_qualified(&p.path) {
149 return Ok(n);
150 }
Scaffold rustnim: Rust->Nim transpiler, type mapping 1a218c2 nandi 9h ago151 let seg = p
152 .path
153 .segments
154 .last()
155 .ok_or_else(|| "empty type path".to_string())?;
156 let name = seg.ident.to_string();
157
158 if let Some(why) = rejected(&name) {
159 return Err(format!("unsupported type `{}`: {}", name, why));
160 }
161
162 let args: Vec<Nim> = match &seg.arguments {
163 PathArguments::AngleBracketed(a) => a
164 .args
165 .iter()
166 .filter_map(|g| match g {
167 GenericArgument::Type(t) => Some(map(t)),
168 _ => None,
169 })
170 .collect::<Result<_, _>>()?,
171 _ => vec![],
172 };
173
174 match (name.as_str(), args.len()) {
175 ("Vec", 1) => Ok(Nim::Seq(Box::new(args[0].clone().owned()))),
176 ("Option", 1) => Ok(Nim::Named("Option".into(), args)),
177 ("Result", 2) => Ok(Nim::Named("Result".into(), args)),
178 ("Box", 1) => Ok(args[0].clone()),
179 _ => {
180 if let Some(p) = prim(&name) {
181 Ok(p)
182 } else {
183 Ok(Nim::Named(name, args))
184 }
185 }
186 }
187 }
188 // &T is a value in Nim; &mut T becomes a `var` parameter. The caller
189 // decides whether a `var` is legal in the position it is used.
190 Type::Reference(r) => {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 7h ago191 // `&str` is a borrowed view of characters, not an owned string.
192 // Nim accepts a `string` argument for an `openArray[char]`
193 // parameter, so a literal still passes straight through.
194 if let Type::Path(p) = &*r.elem {
195 if p.path.is_ident("str") {
196 return Ok(Nim::OpenArray(Box::new(Nim::Prim("char".into()))));
197 }
198 }
Scaffold rustnim: Rust->Nim transpiler, type mapping 1a218c2 nandi 9h ago199 let inner = map(&r.elem)?;
200 if r.mutability.is_some() {
201 Ok(Nim::Var(Box::new(inner)))
202 } else {
203 Ok(inner)
204 }
205 }
206 Type::Slice(s) => Ok(Nim::OpenArray(Box::new(map(&s.elem)?))),
207 Type::Array(a) => {
208 let len = match &a.len {
209 syn::Expr::Lit(syn::ExprLit {
210 lit: syn::Lit::Int(i),
211 ..
212 }) => i
213 .base10_parse::<usize>()
214 .map_err(|e| format!("array length: {}", e))?,
215 _ => return Err("array length must be a literal".into()),
216 };
217 Ok(Nim::Array(len, Box::new(map(&a.elem)?)))
218 }
219 Type::Tuple(t) if t.elems.is_empty() => Ok(Nim::Unit),
220 Type::Tuple(t) => Ok(Nim::Tuple(
221 t.elems.iter().map(map).collect::<Result<_, _>>()?,
222 )),
223 Type::Paren(p) => map(&p.elem),
224 Type::Group(g) => map(&g.elem),
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago225 Type::FnPtr(f) => {
226 let args: Vec<Nim> = f
227 .inputs
228 .iter()
229 .map(|a| map(&a.ty))
230 .collect::<Result<_, _>>()?;
231 Ok(Nim::Proc(args, Box::new(ret_ty(&f.output)?)))
232 }
Scaffold rustnim: Rust->Nim transpiler, type mapping 1a218c2 nandi 9h ago233 Type::ImplTrait(i) => {
234 // `impl AsRef<[u8]>` and friends: fall back to the bound's own
235 // shape where we can recognise it, since Nim has no impl-trait.
236 for b in &i.bounds {
237 if let TypeParamBound::Trait(tb) = b {
238 if let Some(seg) = tb.path.segments.last() {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago239 // `impl Fn(A) -> B` is a callable; Nim has a proc type
240 // for exactly this.
241 if seg.ident == "Fn" || seg.ident == "FnMut" || seg.ident == "FnOnce" {
242 if let PathArguments::Parenthesized(a) = &seg.arguments {
243 let args: Vec<Nim> = a
244 .inputs
245 .iter()
246 .map(|a| map(&a.ty))
247 .collect::<Result<_, _>>()?;
248 return Ok(Nim::Proc(args, Box::new(ret_ty(&a.output)?)));
249 }
250 }
Scaffold rustnim: Rust->Nim transpiler, type mapping 1a218c2 nandi 9h ago251 if seg.ident == "AsRef" || seg.ident == "Into" {
252 if let PathArguments::AngleBracketed(a) = &seg.arguments {
253 for g in &a.args {
254 if let GenericArgument::Type(t) = g {
255 return map(t);
256 }
257 }
258 }
259 }
260 }
261 }
262 }
263 Err("unsupported `impl Trait` type".into())
264 }
265 Type::Infer(_) => Err("inferred type in a position that needs a name".into()),
266 other => Err(format!("unsupported type form: {:?}", discriminant(other))),
267 }
268}
269
270fn discriminant(t: &Type) -> &'static str {
271 match t {
272 Type::Ptr(_) => "raw pointer",
273 Type::TraitObject(_) => "trait object",
274 Type::Never(_) => "never",
275 Type::Macro(_) => "macro",
276 _ => "other",
277 }
278}