nandi/rustnimpublic Fork 0
428f3741e2904549bd0157081162006f3952b324
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 · 269 lines · 9.7 KBRust Blame HistoryRaw
Scaffold rustnim: Rust->Nim transpiler, type mapping 1a218c2 nandi 19h 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 enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago19 /// `impl Fn(A) -> B` / `fn(A) -> B`. `nimcall` is the default calling
20 /// convention for a top-level proc, which is what Rust passes here.
21 Proc(Vec<Nim>, Box<Nim>),
Scaffold rustnim: Rust->Nim transpiler, type mapping 1a218c2 nandi 19h ago22 Unit,
23}
24
25impl Nim {
26 pub fn render(&self) -> String {
27 match self {
28 Nim::Prim(s) => s.clone(),
29 Nim::Seq(t) => format!("seq[{}]", t.render()),
30 Nim::OpenArray(t) => format!("openArray[{}]", t.render()),
31 Nim::Array(n, t) => format!("array[{}, {}]", n, t.render()),
32 Nim::Tuple(ts) => {
33 let inner: Vec<String> = ts.iter().map(|t| t.render()).collect();
34 format!("({})", inner.join(", "))
35 }
36 Nim::Named(n, args) if args.is_empty() => n.clone(),
37 Nim::Named(n, args) => {
38 let inner: Vec<String> = args.iter().map(|t| t.render()).collect();
39 format!("{}[{}]", n, inner.join(", "))
40 }
41 Nim::Var(t) => format!("var {}", t.render()),
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago42 Nim::Proc(args, ret) => {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago43 // Nim's proc types name their parameters even when the name is
44 // never used.
45 let inner: Vec<String> = args
46 .iter()
47 .enumerate()
48 .map(|(i, t)| format!("a{}: {}", i, t.render()))
49 .collect();
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago50 match &**ret {
51 Nim::Unit => format!("proc ({}) {{.nimcall.}}", inner.join(", ")),
52 r => format!("proc ({}): {} {{.nimcall.}}", inner.join(", "), r.render()),
53 }
54 }
Scaffold rustnim: Rust->Nim transpiler, type mapping 1a218c2 nandi 19h ago55 Nim::Unit => "void".into(),
56 }
57 }
58
59 /// Owned form: a borrowed slice parameter is `openArray[T]`, but the same
60 /// type in an owned position (a field, a return value) must be `seq[T]`.
61 pub fn owned(self) -> Nim {
62 match self {
63 Nim::OpenArray(t) => Nim::Seq(t),
64 Nim::Var(t) => t.owned(),
65 other => other,
66 }
67 }
68
69 pub fn is_integer(&self) -> bool {
70 matches!(self, Nim::Prim(p) if matches!(p.as_str(),
71 "int8"|"int16"|"int32"|"int64"|"int"|
72 "uint8"|"uint16"|"uint32"|"uint64"|"uint"))
73 }
74
75 pub fn is_unsigned(&self) -> bool {
76 matches!(self, Nim::Prim(p) if p.starts_with("uint"))
77 }
78}
79
80pub fn prim(name: &str) -> Option<Nim> {
81 let mapped = match name {
82 "i8" => "int8",
83 "i16" => "int16",
84 "i32" => "int32",
85 "i64" => "int64",
86 "isize" => "int",
87 "u8" => "uint8",
88 "u16" => "uint16",
89 "u32" => "uint32",
90 "u64" => "uint64",
91 "usize" => "uint",
92 "f32" => "float32",
93 "f64" => "float64",
94 "bool" => "bool",
95 "char" => "Rune",
96 "str" | "String" => "string",
97 _ => return None,
98 };
99 Some(Nim::Prim(mapped.into()))
100}
101
102/// Types we refuse rather than approximate.
103pub fn rejected(name: &str) -> Option<&'static str> {
104 match name {
105 "i128" | "u128" => Some("128-bit integers have no faithful Nim equivalent"),
106 _ => None,
107 }
108}
109
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago110fn ret_ty(r: &syn::ReturnType) -> Result<Nim, String> {
111 match r {
112 syn::ReturnType::Default => Ok(Nim::Unit),
113 syn::ReturnType::Type(_, t) => Ok(map(t)?.owned()),
114 }
115}
116
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago117/// Types from `core::fmt` that must not be confused with a user type of the
118/// same short name. `fmt::Error` and a crate's own `Error` are different
119/// types, and collapsing a path to its last segment would merge them.
120fn std_qualified(p: &syn::Path) -> Option<Nim> {
121 let segs: Vec<String> = p.segments.iter().map(|s| s.ident.to_string()).collect();
122 if segs.len() < 2 {
123 return None;
124 }
125 let (q, name) = (&segs[segs.len() - 2], segs.last()?.as_str());
126 if q != "fmt" {
127 return None;
128 }
129 Some(Nim::Prim(
130 match name {
131 "Error" => "FmtError",
132 "Formatter" => "Formatter",
133 // `fmt::Result` is `Result<(), fmt::Error>`. A formatting impl is
134 // lowered to a proc that returns the formatted string, so the
135 // result type is erased there; this spelling exists so that a
136 // signature mentioning it still maps to something.
137 "Result" => "FmtResult",
138 _ => return None,
139 }
140 .into(),
141 ))
142}
143
Scaffold rustnim: Rust->Nim transpiler, type mapping 1a218c2 nandi 19h ago144pub fn map(t: &Type) -> Result<Nim, String> {
145 match t {
146 Type::Path(p) => {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago147 if let Some(n) = std_qualified(&p.path) {
148 return Ok(n);
149 }
Scaffold rustnim: Rust->Nim transpiler, type mapping 1a218c2 nandi 19h ago150 let seg = p
151 .path
152 .segments
153 .last()
154 .ok_or_else(|| "empty type path".to_string())?;
155 let name = seg.ident.to_string();
156
157 if let Some(why) = rejected(&name) {
158 return Err(format!("unsupported type `{}`: {}", name, why));
159 }
160
161 let args: Vec<Nim> = match &seg.arguments {
162 PathArguments::AngleBracketed(a) => a
163 .args
164 .iter()
165 .filter_map(|g| match g {
166 GenericArgument::Type(t) => Some(map(t)),
167 _ => None,
168 })
169 .collect::<Result<_, _>>()?,
170 _ => vec![],
171 };
172
173 match (name.as_str(), args.len()) {
174 ("Vec", 1) => Ok(Nim::Seq(Box::new(args[0].clone().owned()))),
175 ("Option", 1) => Ok(Nim::Named("Option".into(), args)),
176 ("Result", 2) => Ok(Nim::Named("Result".into(), args)),
177 ("Box", 1) => Ok(args[0].clone()),
178 _ => {
179 if let Some(p) = prim(&name) {
180 Ok(p)
181 } else {
182 Ok(Nim::Named(name, args))
183 }
184 }
185 }
186 }
187 // &T is a value in Nim; &mut T becomes a `var` parameter. The caller
188 // decides whether a `var` is legal in the position it is used.
189 Type::Reference(r) => {
190 let inner = map(&r.elem)?;
191 if r.mutability.is_some() {
192 Ok(Nim::Var(Box::new(inner)))
193 } else {
194 Ok(inner)
195 }
196 }
197 Type::Slice(s) => Ok(Nim::OpenArray(Box::new(map(&s.elem)?))),
198 Type::Array(a) => {
199 let len = match &a.len {
200 syn::Expr::Lit(syn::ExprLit {
201 lit: syn::Lit::Int(i),
202 ..
203 }) => i
204 .base10_parse::<usize>()
205 .map_err(|e| format!("array length: {}", e))?,
206 _ => return Err("array length must be a literal".into()),
207 };
208 Ok(Nim::Array(len, Box::new(map(&a.elem)?)))
209 }
210 Type::Tuple(t) if t.elems.is_empty() => Ok(Nim::Unit),
211 Type::Tuple(t) => Ok(Nim::Tuple(
212 t.elems.iter().map(map).collect::<Result<_, _>>()?,
213 )),
214 Type::Paren(p) => map(&p.elem),
215 Type::Group(g) => map(&g.elem),
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago216 Type::FnPtr(f) => {
217 let args: Vec<Nim> = f
218 .inputs
219 .iter()
220 .map(|a| map(&a.ty))
221 .collect::<Result<_, _>>()?;
222 Ok(Nim::Proc(args, Box::new(ret_ty(&f.output)?)))
223 }
Scaffold rustnim: Rust->Nim transpiler, type mapping 1a218c2 nandi 19h ago224 Type::ImplTrait(i) => {
225 // `impl AsRef<[u8]>` and friends: fall back to the bound's own
226 // shape where we can recognise it, since Nim has no impl-trait.
227 for b in &i.bounds {
228 if let TypeParamBound::Trait(tb) = b {
229 if let Some(seg) = tb.path.segments.last() {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago230 // `impl Fn(A) -> B` is a callable; Nim has a proc type
231 // for exactly this.
232 if seg.ident == "Fn" || seg.ident == "FnMut" || seg.ident == "FnOnce" {
233 if let PathArguments::Parenthesized(a) = &seg.arguments {
234 let args: Vec<Nim> = a
235 .inputs
236 .iter()
237 .map(|a| map(&a.ty))
238 .collect::<Result<_, _>>()?;
239 return Ok(Nim::Proc(args, Box::new(ret_ty(&a.output)?)));
240 }
241 }
Scaffold rustnim: Rust->Nim transpiler, type mapping 1a218c2 nandi 19h ago242 if seg.ident == "AsRef" || seg.ident == "Into" {
243 if let PathArguments::AngleBracketed(a) = &seg.arguments {
244 for g in &a.args {
245 if let GenericArgument::Type(t) = g {
246 return map(t);
247 }
248 }
249 }
250 }
251 }
252 }
253 }
254 Err("unsupported `impl Trait` type".into())
255 }
256 Type::Infer(_) => Err("inferred type in a position that needs a name".into()),
257 other => Err(format!("unsupported type form: {:?}", discriminant(other))),
258 }
259}
260
261fn discriminant(t: &Type) -> &'static str {
262 match t {
263 Type::Ptr(_) => "raw pointer",
264 Type::TraitObject(_) => "trait object",
265 Type::Never(_) => "never",
266 Type::Macro(_) => "macro",
267 _ => "other",
268 }
269}